This is an old revision of the document!
Data Binding
If XAML markup is going to be used for data binding, you must use an ObjectDataProvider
or XMLDataProvider
. If using an existing object, you can only use DataContext
as your data binding. This requires the object manipulation to be done programmatically only.
Attributes
Mode
To specify the direction and frequency of the binding.
- OneTime
- OneWay
- TwoWay
- OneWayToSource
Converter
To specify a converter to use in data conversion from the target to the source.
ConverterParameter
To specify additional parameters to the converter.
Example:
<UserControl... xmlns:viewmodel="clr-namespace:Acme.App.ViewModel;assembly=Acme.App.ViewModel" xmlns:local="clr-namespace:Acme.App.Types;assembly=Acme.App.Types" ...> <UserControl.Resources> <viewmodel:TInstrumentSideDetectedToVisibilityConverter x:Key="InstrumentSideDetectedToVisibilityConverter"/> </UserControl.Resources> . . . <ToggleButton Visibility="{Binding Path=Fitting.DetectedSide, Converter={StaticResource InstrumentSideDetectedToVisibilityConverter}, ConverterParameter={x:Static local:TSide.Right}}" Content="An Environment"> . . . </UserControl>
Custom type declared in Acme.App.Types.dll assembly:
public enum TSide { Left Right }
Custom ValueConverter declared in Acme.App.ViewModel.dll assembly (view model):
///============================================================================================ /// <summary> /// Class TInstrumentSideDetectedToVisibilityConverter /// </summary> ///============================================================================================ public class TInstrumentSideDetectedToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Visibility _visibility; if ( ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.Left) && ((TSide)parameter == TSide.Left) || ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.Both) && ((TSide)parameter == TSide.Left) || ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.None) && ((TSide)parameter == TSide.Left) || ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.Right) && ((TSide)parameter == TSide.Right) || ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.Both) && ((TSide)parameter == TSide.Right) || ((TInstrumentDetectedResults)value == TInstrumentDetectedResults.None) && ((TSide)parameter == TSide.Right) ) { _visibility = Visibility.Visible; } else { _visibility = Visibility.Collapsed; } return _visibility; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
UpdateSourceTrigger
To specify when the target property is updated.
- LostFocus
- PropertyChanged
- Explicit
FallbackValue
To specify a default value when the binding fails.
Source, RelativeSource, ElementName
Only one of these Binding attributes should be set:
Source
, when referring to an object (see also Binding.Source): Example:<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:src="clr-namespace:SDKSample" SizeToContent="WidthAndHeight" Title="Simple Data Binding Sample"> <Window.Resources> <!-- Instantiate an object --> <src:Person x:Key="myDataSource" PersonName="Joe"/> ... </Window.Resources> ... <TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}"/> </Window>
C# examples:
// Examples Binding my_binding1 = new Binding(); my_binding1.Source = my_data_object; Binding my_binding2 = new Binding("PersonName"); my_binding2.Source = myDataSource;
RelativeSource
, when referring to current element withSelf
,FindAncestor
, andPreviousData
(see also Binding.RelativeSource): Example:<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style>
Using
RelativeSource
to find Ancestor:<DockPanel Background='Blue'> <DockPanel Background='Green'> <TextBox Margin='20' DockPanel.Dock="Top" Background='{Binding Background , RelativeSource={RelativeSource FindAncestor, AncestorType=DockPanel , AncestorLevel=2}}' /> </DockPanel> </DockPanel>
C# examples:
// Examples Binding my_binding1 = new Binding(); my_binding1.RelativeSource = RelativeSource.Self; Binding my_binding2 = new Binding(); my_binding2.RelativeSource = new RelativeSource(RelativeSourceMode.FinAncestor, typeof(StackPanel), 1); Binding my_binding3 = new Binding(); my_binding3.RelativeSource = RelativeSource.TemplatedParent;
ElementName
, when referring to a control (see also Binding.ElementName): Example:<ComboBox Name="myComboBox" SelectedIndex="0"> <ComboBoxItem>Green</ComboBoxItem> <ComboBoxItem>Blue</ComboBoxItem> <ComboBoxItem>Red</ComboBoxItem> </ComboBox> <Canvas> <Canvas.Background> <Binding ElementName="myComboBox" Path="SelectedItem.Content"/> </Canvas.Background> </Canvas>
Or an example using a
Slider
:<Slider Name="Slider1" /> <TextBox Name="TextBox1" Text="{Binding ElementName=Slider1, Path=Value}" />
Alternatively:
<Slider Name="Slider1" /> <TextBox Name="TextBox1"> <TextBox.Text> <Binding ElementName="Slider1" Path="Value" /> </TextBox.Text> </TextBox>
C# examples:
// Examples Binding my_binding1 = new Binding(); my_binding1.ElementName = "btnStart1"; Binding my_binding2 = new Binding("Value"); my_binding2.ElementName = "Slider1";
References: What is the Difference Between ''Source'' and ''RelativeSource''
DataContext
To bind to the default DataContext
of an object (or parent), use:
<ContentControl Name="ccDetails" Content="{Binding}" />
Programmatically in C#:
ccDetails.SetBinding(ContentControl.ContentProperty, new Binding());
If you need to include the path, then use:
<ContentControl Name="ccDetails" Content="{Binding Path=Person.Name}" />
ccDetails.SetBinding(ContentControl.ContentProperty, new Binding("Person.Name"));
If the path contains an index, then use:
<ContentControl Name="ccDetails" Content="{Binding Path=Person.Address[0]}" /> <!-- numeric-based index --> <ContentControl Name="ccDetails" Content="{Binding Path=Person.Address[Home]}" /> <!-- key index -->
ccDetails.SetBinding(ContentControl.ContentProperty, new Binding("Person.Address[0]")); // numeric-based index ccDetails.SetBinding(ContentControl.ContentProperty, new Binding("Person.Address[Home]")); // key-based index
Data Providers
DataContext
Create Binding programmatically in C#:
Product ProdDataSource = new Product("Bicycle"); Binding ProdBinding = new Binding("Name"); ProdBinding.Source = ProdDataSource; // txtProdName is an instance of TextBlock txtProdName.SetBinding(TextBlock.TextProperty, ProdBinding);
Alternatively, for existing object instance:
lstProducts.DataContext = aProdSpecsListObject;
Built-in (Primitive) Types
In XAML:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <UserControl.Resources> <sys:Int32 x:Key="intAge">42</sys:Int32> <sys:String x:Key="strYes">Oui</sys:String> ... <sys:Object .../> <sys:Boolean .../> <sys:Char .../> <sys:String.../> <sys:Decimal .../> <sys:Single .../> <sys:Double .../> <sys:Int16 .../> <sys:Int32 .../> <sys:Int64 .../> <sys:TimeSpan .../> <sys:Uri .../> <sys:Byte .../> <sys:Array .../> </UserControl.Resources> </UserControl>
ObjectDataProvider
This provides binding to .NET data types.
To reference data types defined in a library, add the library reference (such as xmlns:local=“clr-namespace:Acme.Products.Specifications
) to namespace in user control's XAML, then add ObjectDataProvider
resource.
<UserControl x:Class="Acme.Products.Specifications.ProductSpecsControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Acme.Products.Specifications mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="600"> <UserControl.Resources> <ObjectDataProvider x:Key="objDataProviderProductSpecs" ObjectType="{x:Type local:TProductSpecificationList}" /> </UserControl.Resources> .... </UserControl>
Alternatively, as an XAML element:
<UserControl.Resources> <local:TProductSpecificationList x:Key="objDataProviderProductSpecs" /> </UserControl.Resources>
Declaring a data source with parameters vs no parameters:
<!--Data Source--> <!--Creates a simple data source object (but requires parameterless constructor)--> <!--This Method creates the object, then later it assigns the field (ie. field is not assign during construction)--> <prodspecs:TProductSpecificationList x:Key="ProdListDataSource" CustomCompany="Acme" /> <!--Creates data source object, and allow passing data into a constructor --> <ObjectDataProvider x:Key="ProdListDataSource" ObjectType="{x:Type prodspecs:TProductSpecificationList}"> <ObjectDataProvider.ConstructorParameters> <custom:TCustomCompany>Acme</custom:TCustomCompany> </ObjectDataProvider.ConstructorParameters> </ObjectDataProvider>
Define the data type in library. Eg: Acme.Products.Specification.dll
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Acme.Products.Specifications { public class TProductSpecificationList : List<string> { public TCustomCompany CustomCompany = TCustomCompany.Acme; // Parameterless Constructor public TProductSpecificationList(): this(TCustomCompany.Acme) { } // Constructor with Parameters public TProductSpecificationList(TCustomCompany aCustomCompany) { if (CustomCompany == TCustomCompany.Acme) { this.Add("Bicycle"); this.Add("Tricyle"); } } } }
Perform data binding in user control's WPF:
<ListBox ItemsSource="{Binding Source={StaticResource objDataProviderProductSpecs}}"/>
XmlDataProvider
This provides binding to XML data (as a resource, or external file). Create an XML data island resource for the window o user control.
<UserControl x:Class="Acme.Products.Specifications.ProductSpecsControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="600"> <UserControl.Resources> <!--<XmlDataProvider x:Key="dataProviderProductSpecs" XPath="">--> <XmlDataProvider x:Key="dataProviderProductSpecs" XPath="ProductSpecs"> <x:XData> <ProductSpecs xmlns=""> <ProductSpec Type="prod_Bicycle"> <Name>Bicycle</Name> <Wheels>2</Wheels> <Color>Red</Color> </ProductSpec> <ProductSpec Type="prod_Tricycle"> <Name>Tricycle</Name> <Wheels>3</Wheels> <Color>Blue</Color> </ProductSpec> </ProductSpecs> </x:XData> </XmlDataProvider> </UserControl.Resources> .... </UserControl>
Alternatively, put the data in an XML file.
<ProductSpecs xmlns=""> ... </ProductSpecs>
Call the data locally:
<XmlDataProvider x:Key="dataProviderProductSpecs" Source="data\prodspecs.xml" XPath="ProductSpecs"/>
Remotely:
<XmlDataProvider x:Key="dataProviderProductSpecs" Source="http://server/prodspecs.xml" XPath="ProductSpecs"/>
Perform data binding in one of these 2 ways:
<Grid> <!--Method 1--> <ListBox ItemsSource="{Binding Source={StaticResource dataProviderProductSpecs}, XPath=ProductSpec/Name}"/> <!--Method 2--> <ListBox > <ListBox.ItemsSource> <Binding Source="{StaticResource dataProviderProductSpecs}" XPath="ProductSpec/Name"></Binding> </ListBox.ItemsSource> </ListBox> </Grid>
Examples
Syntax
Declaring an ObjectDataProvider
:
<ObjectDataProvider x:Key="ObjDataProviderNAMEHERE" ObjectType="{x:Type local:ClassNAMEHERE}" />
This notation will create an instance of that class, and you use the x:Key
name to access it.
Connecting to a user defined object
<UserControl.Resources> <ObjectDataProvider x:Key="objDataProviderTProductSpecificationList" ObjectType="{x:Type local:TProductSpecificationList}" /> ... </UserControl.Resources> <Grid> <DataGrid ItemsSource="{Binding Source={StaticResource objDataProviderTProductSpecificationList}}" Margin="5" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Type}" Header="Type" /> <DataGridTextColumn Binding="{Binding Name}" Header="Name" /> <DataGridTextColumn Binding="{Binding Wheels}" Header="Wheels" /> <DataGridTextColumn Binding="{Binding Color}" Header="Color" /> </DataGrid.Columns> </DataGrid> </Grid>
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Acme.Products.Specifications { public class TProductSpecification { public string Type { get; set; } public string Name { get; set; } public int Wheels{ get; set; } public string Color { get; set; } public TProductSpecification() { Type = ""; Name = ""; Wheels = 4; Color = ""; } public override string ToString() { return String.Format("{0}={1}", this.Name, this.Type); } } public class TProductSpecificationList : List<TProductSpecification> { private TProductSpecification prod; public TProductSpecificationList() { prod = new TProductSpecification(); prod.Type = "prod_Bicycle"; prod.Name = "Bicycle"; prod.Wheels = 2; prod.Color = "Red"; this.Add(prod); prod = new TProductSpecification(); prod.Type = "prod_Tricycle"; prod.Name = "Tricycle"; prod.Wheels = 3; prod.Color = "Blue"; this.Add(prod); } } }
Connecting to a XAML Object
<Page xmlns:sys='clr-namespace:System;assembly=mscorlib' xmlns:local='clr-namespace:MyLib' /> ... <Page.Resources> <!-- Instantiate a string and load into resource dictionary --> <sys:String x:Key='sample'>ABC 123 DEF 456 </sys:String> <!-- Instantiate an employee and load into resource dictionary --> <local:Employee x:Key='emp' FirstName='Jimmy' LastName='Crickett' Salary='2500' /> </Page.Resources> ... <!-- XAML Binding to string array --> <TextBlock Text="{Binding Source= {StaticResource sample}}" /> <TextBlock Text="{Binding Source= {StaticResource sample},Path=[2]}" /> ... <!-- XAML Binding to Employee instance --> <TextBlock Text="{Binding Source={StaticResource emp},Path=LastName}" />
Source: DevX: Flexible and Powerful Data Binding with WPF, Part 2
And in C# you can access that instance as well:
void Window_Loaded(object sender, RoutedEventArgs args) { DataConnection dc1 = this.FindResource("emp") as DataConnection; DataConnection dc2 = this.Resources["emp"] as DataConnection; }
FindResource(“emp”)
will search the element's resource dictionary as well as any parent elements' resource dictionaries and the application resources. Resources[“emp”]
will search only the resource dictionary of that element.
The documentation recommends the first approach for normal resource lookups, but provides the second approach for when you are retrieving resources from a “known resource dictionary location … to avoid the possible performance and scope implications of run-time key lookup.
Connecting to a Local Property
You must use Dependency Properties to connect to a local property using data binding.
First, define the Dependency Property in C#:
using System.Windows; namespace SOTCBindingValidation { public partial class Window1 : Window { public static readonly DependencyProperty IPAddressProperty = DependencyProperty.Register("IPAddress", typeof(string), typeof(Window1), new UIPropertyMetadata("1.1.1.1")); public string IPAddress { get { return (string)GetValue(IPAddressProperty); } set { SetValue(IPAddressProperty, value); } } public Window1() { InitializeComponent(); } } }
Then make a reference to the required property in XAML:
<Window x:Class="SOTCBindingValidation.Window1" x:Name="This" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SOTCBindingValidation" Title="SOTC Validation Test" Height="150" Width="400"> <Window.Resources> <local:ErrorsToMessageConverter x:Key="eToMConverter" /> </Window.Resources> <StackPanel Margin="5"> <TextBlock Margin="2">Enter An IPv4 Address:</TextBlock> <TextBox x:Name="AddressBox"> <TextBox.Text> <Binding ElementName="This" Path="IPAddress" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:IPv4ValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <TextBlock Margin="2" Foreground="Red" FontWeight="Bold" Text="{Binding ElementName=AddressBox, Path=(Validation.Errors), Converter={StaticResource eToMConverter}}" /> </StackPanel> </Window>
Source: WPF - Binding Validation Rules
Connecting to an XML File
XML File:
<?xml version="1.0"?> <Workshops xmlns=""> <Workshop> <Name>Acme's 12th Annual Continuing Education Workshop</Name> <RecID>12</RecID> <WorkshopID>AcmeWorkshop12</WorkshopID> <VenueInfo> <Organization>Orlando Marriott Lake Mary</Organization> <AddressLn1>1501 International Parkway</AddressLn1> <City>Lake Mary</City> <StateProv>FL</StateProv> <PostalCode>32746</PostalCode> <PhoneHome>407.995.1100</PhoneHome> <PhoneWork>800.380.7724</PhoneWork> <Fax>407.995.1101</Fax> <Website>www.marriotthotels.com</Website> </VenueInfo> <Days> <DateTime>2011/01/14</DateTime> <DateTime>2011/01/15</DateTime> </Days> <PricingDailyPackages> <PricingPackage> <Name>Premium</Name> <Pricing>100</Pricing> </PricingPackage> <PricingPackage> <Name>Standard</Name> <Pricing>200</Pricing> </PricingPackage> <PricingPackage> <Name>Other</Name> <Pricing>200</Pricing> </PricingPackage> </PricingDailyPackages> </Workshop> <Workshop> <Name>Acme's 13th Annual Continuing Education Workshop</Name> <RecID>13</RecID> <WorkshopID>AcmeWorkshop13</WorkshopID> <VenueInfo> <Organization>Orlando Marriott Lake Mary</Organization> <AddressLn1>1501 International Parkway</AddressLn1> <City>Lake Mary</City> <StateProv>FL</StateProv> <PostalCode>32746</PostalCode> <PhoneHome>407.995.1100</PhoneHome> <PhoneWork>800.380.7724</PhoneWork> <Fax>407.995.1101</Fax> <Website>www.marriotthotels.com</Website> </VenueInfo> <Days> <DateTime>2012/01/24</DateTime> <DateTime>2012/01/25</DateTime> </Days> <PricingDailyPackages> <PricingPackage> <Name>Premium</Name> <Pricing>100</Pricing> </PricingPackage> <PricingPackage> <Name>Standard</Name> <Pricing>200</Pricing> </PricingPackage> <PricingPackage> <Name>Other</Name> <Pricing>200</Pricing> </PricingPackage> </PricingDailyPackages> </Workshop> </Workshops>
Data binding for a DataGrid
:
<UserControl ...> <UserControl.Resources> <XmlDataProvider x:Key="dataProviderWorkshopList" Source="Data\Workshops.xml" XPath="Workshops/Workshop"/> </UserControl.Resources> ... <DataGrid Name="datagridWorkshopList" ItemsSource="{Binding Source={StaticResource dataProviderWorkshopList}}" CanUserAddRows="False" IsReadOnly="True" FrozenColumnCount="1" IsSynchronizedWithCurrentItem="True" AlternatingRowBackground="AliceBlue" AlternationCount="2" Margin="5" AutoGenerateColumns="False" > <DataGrid.Columns> <DataGridTemplateColumn Header="Name" Width="120"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Button Content="{Binding XPath=WorkshopID}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn> <DataGridTextColumn Binding="{Binding XPath=Name}" Header="Name" /> <DataGridTextColumn Binding="{Binding XPath=RecID}" Header="Rec ID" /> <DataGridTextColumn Binding="{Binding XPath=WorkshopID}" Header="Workshop ID" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/Organization}" Header="Venue" Width="100"/> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/AddressLn1}" Header="Address Line 1" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/AddressLn2}" Header="Address Line 2" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/AddressLn3}" Header="Address Line 3" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/City}" Header="City" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/StateProv}" Header="State" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/PostalCode}" Header="ZIP Code" /> <DataGridTextColumn Binding="{Binding XPath=VenueInfo/Website}" Header="Website" /> </DataGrid.Columns> </DataGrid> ... </UserControl>
Data binding for a ListBox
:
<UserControl ...> <UserControl.Resources> <XmlDataProvider x:Key="dataProviderWorkshopList" Source="Data\Workshops.xml" XPath="Workshops/Workshop"/> </UserControl.Resources> ... <TextBlock>Workshops</TextBlock> <!--<ListBox Name="lstWorkshops" ItemsSource="{Binding Source={StaticResource dataProviderWorkshopList}}" IsSynchronizedWithCurrentItem="True">--> <ListBox Name="lstWorkshops" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemsSource> <Binding Source="{StaticResource dataProviderWorkshopList}" /> </ListBox.ItemsSource> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding XPath=WorkshopID}" Margin="0,0,5,0" /> <TextBlock>|</TextBlock> <TextBlock Text="{Binding XPath=RecID}" Margin="0,0,5,0" /> <TextBlock>|</TextBlock> <TextBlock Text="{Binding XPath=Name}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> ... </UserControl>
Connecting to a ''DataTable''
<UserControl.Resources> <ObjectDataProvider x:Key="objDataProvProdSpecDataTable" ObjectType="{x:Type local:ProductSpecsControlClass}" /> ... </UserControl.Resources> <Grid> <DataGrid ItemsSource="{Binding Source={StaticResource objDataProvProdSpecDataTable}, Path=ProdSpecDataTable}" Margin="5" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Type}" Header="Type" /> <DataGridTextColumn Binding="{Binding Name}" Header="Name" /> <DataGridTextColumn Binding="{Binding Wheels}" Header="Wheels" /> <DataGridTextColumn Binding="{Binding Color}" Header="Color" /> </DataGrid.Columns> </DataGrid> </Grid>
using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Acme.Products.Specifications { /// <summary> /// Interaction logic for ProductSpecsControl.xaml /// </summary> public partial class ProductSpecsControlClass : UserControl { private DataTable _ProdSpecDataTable; public DataTable ProdSpecDataTable { get { return _ProdSpecDataTable; } } public ProductSpecsControl() { _ProdSpecDataTable= new DataTable(); _ProdSpecDataTable.Columns.Add(new DataColumn("Type", typeof(string))); _ProdSpecDataTable.Columns.Add(new DataColumn("Name", typeof(string))); _ProdSpecDataTable.Columns.Add(new DataColumn("Wheels", typeof(int))); _ProdSpecDataTable.Columns.Add(new DataColumn("Color", typeof(string))); //var row = _ProdSpecDataTable.NewRow(); DataRow row = _ProdSpecDataTable.NewRow(); _ProdSpecData.Rows.Add(row); row["Type"] = "prod_Bicycle"; row["Name"] = "Bicycle"; row["Wheels"] = 2; row["Color"] = "Red"; row = _ProdSpecDataTable.NewRow(); _ProdSpecData.Rows.Add(row); row["Type"] = "prod_Tricycle"; row["Name"] = "Tricycle"; row["Wheels"] = 3; row["Color"] = "Blue"; InitializeComponent(); } } }
Connecting a ListBox to a List of Objects
Define the object list. PatientInfo.cs
:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.ObjectModel; namespace DataBinding.Patient { #region Patient Information record definition /// <summary> /// Patient Information class /// </summary> public class TPatientInfo { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } } #endregion #region Patient List using ObservableCollection /// <summary> /// Patient List using ObservableCollection<> (instead of List<>). /// This approach allows the ListBox to see any updates performed /// on the actual data model. /// </summary> public class TPatientListOC: ObservableCollection<TPatientInfo> { public void PopulateList() { TPatientInfo pat = new TPatientInfo(); pat.LastName = "Doe"; pat.FirstName = "John"; pat.ID = 11; this.Add(pat); pat = new TPatientInfo(); pat.LastName = "Smith"; pat.FirstName = "John"; pat.ID = 22; this.Add(pat); pat = new TPatientInfo(); pat.LastName = "Casell"; pat.FirstName = "Mario"; pat.ID = 33; this.Add(pat); } } #endregion }
Declarative Method
Declare an instance to list of objects, then databind a ListBox
to it. Note that ListBox
needs a DataContext
and DataTemplate
defined for it to work. ucPatientInfo.xaml
:
<UserControl x:Class="DataBinding.Patient.ucPatientInfo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:patient="clr-namespace:DataBinding.Patient" mc:Ignorable="d" Loaded="UserControl_Loaded" d:DesignHeight="300" d:DesignWidth="500"> <UserControl.Resources> <patient:TPatientListOC x:Key="PatientListOCDS"/> </UserControl.Resources> <Grid> <StackPanel Orientation="Vertical"> <!--Patient List--> <TextBlock FontWeight="Bold">Patient List</TextBlock> <StackPanel Orientation="Horizontal"> <!--<ListBox ItemsSource="{Binding Source={StaticResource PatientListOCDS}}" >--> <ListBox Name="lstPatientList" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemsSource> <Binding Source="{StaticResource PatientListOCDS}" /> </ListBox.ItemsSource> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=LastName}" Margin="0,0,5,0" /> <TextBlock Text="{Binding Path=FirstName}" Margin="0,0,5,0" /> <TextBlock Text="{Binding Path=ID}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <StackPanel Orientation="Horizontal"> <Button Name="btnLoadList" Width="100" Click="btnLoadList_Click">Load List</Button> <Button Name="btnClearList" Width="100" Click="btnClearList_Click">Clear List</Button> <Button Name="btnDeleteSelection" Width="100" Click="btnDeleteSelection_Click">Delete Selection</Button> </StackPanel> </StackPanel> <!--Record Details--> <StackPanel DataContext="{Binding Source={StaticResource PatientListOCDS}}"> <TextBlock></TextBlock> <TextBlock FontWeight="Bold">Record Details</TextBlock> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">Last Name</TextBlock> <TextBox Name="txtLastName" Width="300" Text="{Binding Path=LastName}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">First Name</TextBlock> <TextBox Name="txtFirstName" Width="300" Text="{Binding Path=FirstName}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">ID</TextBlock> <TextBox Name="txtID" Width="300" Text="{Binding Path=ID}"/> </StackPanel> <!--Buttons--> <StackPanel Orientation="Horizontal"> <Button Name="btnAdd" Width="100" Click="btnAdd_Click">Add</Button> </StackPanel> </StackPanel> </StackPanel> </Grid> </UserControl>
ucPatientInfo.xaml.cs
:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace DataBinding.Patient { /// <summary> /// Interaction logic for ucDataBinding.xaml /// </summary> public partial class ucPatientInfo : UserControl { private TPatientListOC patlist = new TPatientListOC(); public ucPatientInfo() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { patlist = (TPatientListOC)FindResource("PatientListOCDS"); //patlist.PopulateList(); } private void btnLoadList_Click(object sender, RoutedEventArgs e) { patlist.PopulateList(); } private void btnAdd_Click(object sender, RoutedEventArgs e) { if (txtLastName.Text != "" && txtFirstName.Text != "" && txtID.Text != "") { TPatientInfo pat = new TPatientInfo(); pat.LastName = txtLastName.Text; pat.FirstName = txtFirstName.Text; pat.ID = Convert.ToInt32(txtID.Text); patlist.Add(pat); } else { MessageBox.Show("Please fill in all fields with valid data before adding record."); } } private void btnClearList_Click(object sender, RoutedEventArgs e) { patlist.Clear(); } private void btnDeleteSelection_Click(object sender, RoutedEventArgs e) { patlist.RemoveAt(lstPatientList.SelectedIndex); } } }
Programmatic Method
Programmaticaly create an instance to a list of objects, then programmatically databind a ListBox
to it. Note that the data binding is set to the ListBox
's DataContext
, and requires a DataTemplate
defined for it to work.
ucPatientInfo.xaml
:
<UserControl x:Class="DataBinding.Patient.ucPatientInfo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:patient="clr-namespace:DataBinding.Patient" mc:Ignorable="d" Loaded="UserControl_Loaded" d:DesignHeight="300" d:DesignWidth="500"> <Grid> <StackPanel Orientation="Vertical"> <!--Patient List--> <TextBlock FontWeight="Bold">Patient List</TextBlock> <StackPanel Orientation="Horizontal"> <ListBox Name="lstPatientList" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=LastName}" Margin="0,0,5,0" /> <TextBlock Text="{Binding Path=FirstName}" Margin="0,0,5,0" /> <TextBlock Text="{Binding Path=ID}"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <StackPanel Orientation="Horizontal"> <Button Name="btnLoadList" Width="100" Click="btnLoadList_Click">Load List</Button> <Button Name="btnClearList" Width="100" Click="btnClearList_Click">Clear List</Button> <Button Name="btnDeleteSelection" Width="100" Click="btnDeleteSelection_Click">Delete Selection</Button> </StackPanel> </StackPanel> <!--Record Details--> <StackPanel> <TextBlock></TextBlock> <TextBlock FontWeight="Bold">Record Details</TextBlock> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">Last Name</TextBlock> <TextBox Name="txtLastName" Width="300" Text="{Binding Path=LastName}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">First Name</TextBlock> <TextBox Name="txtFirstName" Width="300" Text="{Binding Path=FirstName}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Width="100">ID</TextBlock> <TextBox Name="txtID" Width="300" Text="{Binding Path=ID}"/> </StackPanel> <!--Buttons--> <StackPanel Orientation="Horizontal"> <Button Name="btnAdd" Width="100" Click="btnAdd_Click">Add</Button> </StackPanel> </StackPanel> </StackPanel> </Grid> </UserControl>
ucPatientInfo.xaml.cs
:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace DataBinding.Patient { /// <summary> /// Interaction logic for ucDataBinding.xaml /// </summary> public partial class ucPatientInfo : UserControl { private TPatientListOC patlist = new TPatientListOC(); public ucPatientInfo() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { DataBinding(); } //--------------------------------------------------------------------------- // Create Data Binding programmatically. //--------------------------------------------------------------------------- private void DataBinding() { Binding bindListItems = new Binding(); bindListItems.Source = patlist; bindListItems.Mode = BindingMode.OneWay; // TwoWay does not work for ListBox lstPatientList.SetBinding(ListBox.ItemsSourceProperty, bindListItems); } private void btnLoadList_Click(object sender, RoutedEventArgs e) { patlist.PopulateList(); } private void btnAdd_Click(object sender, RoutedEventArgs e) { if (txtLastName.Text != "" && txtFirstName.Text != "" && txtID.Text != "") { TPatientInfo pat = new TPatientInfo(); pat.LastName = txtLastName.Text; pat.FirstName = txtFirstName.Text; pat.ID = Convert.ToInt32(txtID.Text); patlist.Add(pat); } else { MessageBox.Show("Please fill in all fields with valid data before adding record."); } } private void btnClearList_Click(object sender, RoutedEventArgs e) { patlist.Clear(); } private void btnDeleteSelection_Click(object sender, RoutedEventArgs e) { patlist.RemoveAt(lstPatientList.SelectedIndex); } } }
IValueConverter
Data binding to properties that do not match the type require a value converter (using IValueConverter
). For example, converting from Visibility
(Visible
, Hidden
, or Collapsed
) into bool
:
// using System.Windows.Data; // Found in PresentationFramework assembly public class TBoolToVisibilityConverter: IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { Visibility _visibility; if ((bool)value == true) { _visibility = Visibility.Visible; } else { _visibility = Visibility.Collapsed; } return _visibility; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
Use it as:
<UserControl ...> <UserControl.Resources> <!--IValueConverter--> <local:TBoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/> ... </UserControl.Resources> <StackPanel Name="pnlBluetoothCompatible" Orientation="Horizontal" Visibility="{Binding Path=BluetoothCompatible, Converter={StaticResource BoolToVisibilityConverter}}"> <TextBlock FontWeight="Bold" Width="120" HorizontalAlignment="Left">Bluetooth Support</TextBlock> <CheckBox IsChecked="{Binding Path=BluetoothCompatible}" SourceUpdated="CheckBox_SourceUpdated"/> </StackPanel> ... </UserControl>
A value converter for an image path that needs the full path prefixed to it:
// using System.Windows.Data; // Found in PresentationFramework assembly public class TImgPathConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string str = (string)value; return "pack://application:,,,/AHI.Products.Specifications;component/" + str; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
<UserControl ...> <UserControl.Resources> <!--IValueConverter--> <local:TImgPathConverter x:Key="ImgPathConverter"/> ... </UserControl.Resources> <StackPanel Orientation="Horizontal"> <TextBlock FontWeight="Bold" Width="120" HorizontalAlignment="Left">Shell/Case View</TextBlock> <Image Source="{Binding Path=HousingFilename, Converter={StaticResource ImgPathConverter}}" Width="40" /> </StackPanel> ... </UserControl>
Microsoft's example:
// using System.Windows; // Found in WindowsBase assembly // using System.Windows.Data; // Found in PresentationFramework assembly [ValueConversion(typeof(DateTime), typeof(String))] public class DateConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { DateTime date = (DateTime)value; return date.ToShortDateString(); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string strValue = value as string; DateTime resultDateTime; if (DateTime.TryParse(strValue, out resultDateTime)) { return resultDateTime; } return DependencyProperty.UnsetValue; } }
XML:
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0,0,8,0" Name="startDateTitle" Style="{StaticResource smallTitleStyle}">Start Date:</TextBlock> <TextBlock Name="StartDateDTKey" Grid.Row="2" Grid.Column="1" Text="{Binding Path=StartDate, Converter={StaticResource dateConverter}}" Style="{StaticResource textStyleTextBlock}"/>
Source: MSDN: IValueConverter Interface
IValueConverter using Parameters
Example 1
XAML:
<UserControl.Resources> <local:StringFormatConverter x:Name="stringFormatter"/> </UserControl.Resources> <StackPanel Orientation="Vertical"> <StackPanel.DataContext> <SilverlightApplication3:Person/> </StackPanel.DataContext> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Mode=OneWay, Path=Name}" TextWrapping="Wrap"/> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text="{Binding Mode=OneWay, Path=LastName}" TextWrapping="Wrap"/> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text='{Binding Mode=OneWay, Path=DOB, Converter={StaticResource stringFormatter}, ConverterParameter="d"}' TextWrapping="Wrap"/> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text='{Binding Mode=OneWay, Path=Age, Converter={StaticResource stringFormatter}, ConverterParameter="c"}' TextWrapping="Wrap"/> <TextBlock HorizontalAlignment="Left" VerticalAlignment="Top" Text='{Binding Mode=OneWay, Path=AnualIncome, Converter={StaticResource stringFormatter}, ConverterParameter="0.00"}' TextWrapping="Wrap"/> </StackPanel>
C#:
public class StringFormatConverter:IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { string formatString = parameter.ToString(); return String.Format("{0:" + formatString + "}", value); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException("This Convert supports only OneWay binding"); } #endregion }
Example 2
XAML:
<ItemsControl ItemsSource="{Binding Converter={StaticResource PropertyViewer}}"/>
Customizing the PropertyViewer is also easy:
<ItemsControl ItemsSource="{Binding Converter={StaticResource PropertyViewer},
ConverterParameter='Species EatsBugs RelativeMass'}">
And the C# code behind:
public class PropertyViewer : IValueConverter { /// <summary> /// Implements IValueConverter.Convert. /// </summary> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // Fetch parameters var propertyInfos = (null != value) ? value.GetType().GetProperties() : new PropertyInfo[0]; var propertyNames = (null != parameter) ? parameter.ToString().Split() : new string[0]; // Prepare a collection to return var propertyDetails = new List<PropertyDetails>(); // No names specified (or no value/properties available) if ((0 == propertyNames.Length) || (0 == propertyInfos.Length)) { // Add all properties to the collection foreach (var propertyInfo in propertyInfos) { propertyDetails.Add(new PropertyDetails(GetPropertyName(propertyInfo), GetPropertyValue(propertyInfo, value))); } } else { // For each property name specified foreach (var propertyName in propertyNames) { // Try to match the name against each property in turn bool found = false; foreach (var propertyInfo in propertyInfos) { if (propertyName == propertyInfo.Name) { // Match; add the property to the collection propertyDetails.Add(new PropertyDetails(GetPropertyName(propertyInfo), GetPropertyValue(propertyInfo, value))); found = true; break; } } if (!found && (0 < propertyName.Length)) { // No matches (and not an empty name); throw an exception throw new ArgumentException("Property \"" + propertyName + "\" does not exist on object " + value + "."); } } } // Return the collection return propertyDetails; } /// <summary> /// Implements IValueConverter.ConvertBack. /// </summary> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException("PropertyViewer does not use ConvertBack."); } /// <summary> /// Gets the name of the specified property. /// </summary> /// <param name="propertyInfo">property</param> /// <returns>name</returns> private static string GetPropertyName(PropertyInfo propertyInfo) { // Look for DisplayNameAttribute var displayNameAttributes = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true); // Return first DisplayNameAttribute.DisplayName or property name if none present return (0 < displayNameAttributes.Length) ? ((DisplayNameAttribute)(displayNameAttributes[0])).DisplayName : propertyInfo.Name; } /// <summary> /// Gets the value of the specified property for an instance. /// </summary> /// <param name="propertyInfo">property</param> /// <param name="instance">instance</param> /// <returns>value</returns> private static object GetPropertyValue(PropertyInfo propertyInfo, object instance) { try { // Return instance's value return propertyInfo.GetValue(instance, null); } catch (TargetParameterCountException) { // Likely an indexer (ex: Array.Items); skip complex properties here return null; } } }
Source: IValueConverter: The Swiss Army Knife of Bindings...
Multi-Binding
To display 2 different variables in a same field (eg: 3.14 = May 5, 2012):
<TextBox> <TextBox.Text> <MultiBinding StringFormat="{}{0:F2} = {1:D}"> <Binding Path="Double" /> <Binding Path="Date"/> </MultiBinding> </TextBox.Text> </TextBox>
Debugging
- Enable WPF debug output. In Visual Studio, Options > Debugging > Output Window > WPF Trace Settings > Data Binding > All.
- Add a high TraceLevel to your binding:
PresentationTraceSources.SetTraceLevel(NewBinding, PresentationTraceLevel.High);
- Run application, and review log in “Output” tab in Visual Studio.
- Add
System.Diagnostics
entry toapp.exe.config
. See:
See Also
- ObservableCollection and Item PropertyChanged