Showing posts with label MVVM. Show all posts
Showing posts with label MVVM. Show all posts

Thursday, 6 June 2013

WPF Binding Integer Validation with Null Values

I found a small hole in an application I am developing where a null value in a textbox bound to an integer value wouldn’t get validated.

It was slightly more complicated than that however. The xaml was as follows;

<TextBox Text="{Binding MyTextBoxText, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay}"/>

Where my Validation was set for when the control lost focus.

When I added a new record, the value of the binding was set to null, this enforced by validation correctly. Same when I edited the text and left the control. However, if I edited the control when it had a value, say changing the value from a 2 to nothing, then the validation wouldn’t occur.

This was because the binding didn’t have a value for a null entry. Adding a TargetNullValue parameter to the binding and thus changing the binding to the following fixed the issue nicely;



<TextBox Text="{Binding TaskOrder, UpdateSourceTrigger=LostFocus, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay, TargetNullValue=''}"/>

More information on the TargetNullValue Property can be found here;

http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.targetnullvalue.aspx

Tuesday, 21 May 2013

Binding the WPF WebBrowser Source Property

In an MVVM application I am currently creating, I had the need to embed the WPF WebBrowser control and quickly discovered that the Source Property is not a dependancy object, and so cannot be bound.

I quickly search around on StackOverflow.com and found a neat solution in c#;

http://stackoverflow.com/a/265648/1305169

The VB Version of which is;

   1: Namespace Helpers
   2:  
   3:     ''' <summary>
   4:     ''' Allows for the Source Property of the WebBrowser to be Bindable to the DataContext
   5:     ''' </summary>
   6:     ''' <remarks></remarks>
   7:     Public Class WebBrowserUtility
   8:         Private Sub New()
   9:         End Sub
  10:         Public Shared ReadOnly BindableSourceProperty As DependencyProperty = DependencyProperty.RegisterAttached("BindableSource", GetType(String), GetType(WebBrowserUtility), New UIPropertyMetadata(Nothing, AddressOf BindableSourcePropertyChanged))
  11:  
  12:         Public Shared Function GetBindableSource(obj As DependencyObject) As String
  13:             Return DirectCast(obj.GetValue(BindableSourceProperty), String)
  14:         End Function
  15:  
  16:         Public Shared Sub SetBindableSource(obj As DependencyObject, value As String)
  17:             obj.SetValue(BindableSourceProperty, value)
  18:         End Sub
  19:  
  20:         Public Shared Sub BindableSourcePropertyChanged(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
  21:             Dim browser As WebBrowser = TryCast(o, WebBrowser)
  22:             If browser IsNot Nothing Then
  23:                 Dim uri As String = TryCast(e.NewValue, String)
  24:                 browser.Source = If(uri IsNot Nothing, New Uri(uri), Nothing)
  25:             End If
  26:         End Sub
  27:     End Class
  28:  
  29: End Namespace

You then use your new Bindable Property in the XAML as;



<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
    xmlns:Helpers="clr-namespace:FrontEnd.Helpers"
    x:Class="frmKiosk"
    x:Name="Window"
    
    Width="1920" Height="1080" WindowStyle="None" WindowStartupLocation="CenterScreen" WindowState="Maximized" Background="#FF1327D0" Cursor="None">
 
    <WebBrowser x:Name="wbMain" Helpers:WebBrowserUtility.BindableSource="{Binding KioskWebAddress}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
 
</Window>

Then simply creating a Property in the DataContext, and setting the source like;



KioskWebAddress = "c:/HTML/index.html"

Tuesday, 26 March 2013

WPF - Programmatically Adding Buttons the MVVM Way

I recently came across a requirement to Programmatically Add Buttons to a WPF View in my MVVM based app. Obviously this is not entirely trivial as each Button must contain the correct Bindings to interact with the underlying ViewModel.

The way I tackled this was to use an ItemsControl, with a Canvas Control in the ItemsPanelTemplate. I then added a DataTemplate to the ItemTemplate, with our Button Template in this.

This Button Template contained the relevant binding to hook up the Command Property to my ViewModel.

For ease of use, I then created a Class which housed the various Properties I wanted to expose to each Button, such as it’s Position, CommandParameter and Content, and allowed me to return a Tickness for the Button Position.

From here I created an ObservableCollection of my new Button Class, and bound this to the ItemsControl.

The code can be seen below;

XAML:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Canvas x:Name="MyCanvas">
        <ItemsControl ItemsSource="{Binding MyButtons}" Height="237" Width="507">
            <ItemsControl.ItemsPanel >
                <ItemsPanelTemplate>
                    <Canvas IsItemsHost="true"></Canvas>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Margin="{Binding ControlMargin}" Content="{Binding Content}" Command="{Binding DataContext.ButtonCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding ProductId}"></Button>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Canvas>
</Window>

FluidButton Class:



 

Public Class FluidButton
 
    Public Property Content As String
    Public Property LeftPos As Double
    Public Property TopPos As Double
    Public Property ProductId As Double
 
    ''' <summary>
    ''' Returns the Control Margin, using the Class Properties
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ControlMargin As Thickness
        Get
            Return New Thickness With {.Left = LeftPos, .Top = TopPos}
        End Get
    End Property
    
End Class

Properties:



 

    ''' <summary>
    ''' Our Collection of Buttons or Products
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property MyButtons As ObservableCollection(Of FluidButton)
 
    ''' <summary>
    ''' Used to expose the Button Pressed Execute Commands to the UI for Binding
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Newed up in the Form Load Event</remarks>
    Public Property ButtonCommand As DelegateCommand
Adding Buttons:


 

MyButtons = New ObservableCollection(Of FluidButton)
 
MyButtons.Add(New FluidButton With {.Content = "Test1", .LeftPos = 0, .TopPos = 20, .ProductId = 1})
MyButtons.Add(New FluidButton With {.Content = "Test2", .LeftPos = 40, .TopPos = 30, .ProductId = 2})
MyButtons.Add(New FluidButton With {.Content = "Test3", .LeftPos = 80, .TopPos = 40, .ProductId = 3})
MyButtons.Add(New FluidButton With {.Content = "Test4", .LeftPos = 120, .TopPos = 50, .ProductId = 4})
MyButtons.Add(New FluidButton With {.Content = "Test5", .LeftPos = 160, .TopPos = 60, .ProductId = 5})

Sunday, 3 February 2013

WPF Error Adorner Visibility Binding MVVM Style

While working on an Application for a Client recently, I found that I needed to be able to control the visibility of the WPF Error Adorners.

These error adorners are a great way of giving the end user a visual representation of where any Validation Errors exist. However, as a result of their design, these Adorners will always appear ontop of all other elements. This causes a problem of course, when using anything such as a Customer Message box. The very thing required to notify the User of any Validation Errors on Save for example.

So, I decided to look at ways of controlling, globally, the Visibility of the Error Adorners. This became rather troublesome, After some trial and error I realised that I could reach the DataContext that the Error Adorner was placed within by refering to the AdornedElement’s Parent.

Once I realised this, I added a ShowErrors Boolean Property to my DataContext, and bound the Visibility of the AdornedElement to this.

The XAML Code was;

<Converters:BolVisibilityConverter x:Key="MyBolVisibilityConverter"/>
<Style TargetType="{x:Type TextBox}">
      <Setter Property="VerticalAlignment" Value="Center" />
      <Setter Property="Margin" Value="0,2,40,2" />
      <Setter Property="Validation.ErrorTemplate">
          <Setter.Value>
              <ControlTemplate>
                        <DockPanel LastChildFill="true" Visibility="{Binding ElementName=customAdorner, Path=AdornedElement.Parent.DataContext.ShowErrors, Converter={StaticResource MyBolVisibilityConverter}, Mode=TwoWay}">
                            <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="20" Height="20" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                                </TextBlock>
                          </Border>
                          <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                         <Border BorderBrush="red" BorderThickness="1" />
                       </AdornedElementPlaceholder>
                   </DockPanel>
               </ControlTemplate>
           </Setter.Value>
      </Setter>
</Style>


I needed to use a Value Converter here, to convert my Boolean to a Visibility Value. The Converter Code was;



Namespace Converters
 
    Public Class BolVisibilityConverter
        Implements IValueConverter
 
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
 
            If value Is Nothing OrElse value = False Then
 
                Return Visibility.Hidden
 
            Else
 
                Return Visibility.Visible
 
            End If
 
        End Function
 
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
            Return DirectCast(value, Boolean)
 
        End Function
    End Class
 
End Namespace

I was then able to control the Adorner Visibility simply by setting the ShowErrors Property to either True or False

Thursday, 24 January 2013

WPF MVVM ComboBox Binding to XML Data.

Once again, while browsing StackOverflow.com, I came across a question from a user who needed to fix their ComboBox Binding. The user in question was Binding XML data in Key/Value pairs to the combo box, displaying the Key and wanted to retrieve the Value.

This was achieved using the following example;

My XML Test Data was saved as XMLFile1.xml;

<?xml version="1.0" encoding="utf-8"?>
<Colours>
  <Colour ID="1" Name="None" />
  <Colour ID="2" Name="Red" />
  <Colour ID="3" Name="White" />
</Colours>


My XAML was;






<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="74" Width="150">
    <Window.Resources>
        <XmlDataProvider x:Key="ColourData" Source="/XMLFile1.xml" XPath="Colours" />
    </Window.Resources>
    <Grid>
        <ComboBox x:Name="cmbColour" HorizontalAlignment="Left" Margin="10,10,0,0" 
                  VerticalAlignment="Top" Width="120" DisplayMemberPath="@ID" 
                  ItemsSource="{Binding Source={StaticResource ColourData}, XPath=./Colour}" 
                  SelectedValuePath="@Name"
                  SelectedValue="{Binding SelectedColourValue}"
                  />
    </Grid>
</Window>


And finally my ViewModel was;






Imports System.ComponentModel
Imports System.Xml
 
Class MainWindow : Implements INotifyPropertyChanged
 
    Private _SelectedColourValue As String
    Public Property SelectedColourValue As String
        Get
            Return _SelectedColourValue
 
        End Get
        Set(value As String)
 
            If value.Equals(_SelectedColourValue) = False Then
 
                _SelectedColourValue = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("SelectedColourValue"))
 
            End If
 
        End Set

End Property


    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
 
    Public Sub New()
 
        ' This call is required by the designer.
        InitializeComponent()
 
        ' Add any initialization after the InitializeComponent() call.
        me.DataContext = Me
 
    End Sub
End Class


This successfully displayed the ID property in the ComboBox, and when selected the “SelectedColourValue” Property held the Value.