A popular implementation of the PropertyChanged event looks like this
if (!Object.Equals(storage, value))
storage = value;
}
Start a new Visual Studio C#, WPF application. I used .Net Core. Call it "BindingToDataTable" (slightly wrong because it's binding to an integer that is the problem).
The XAML defines three combo boxes that are bound to the same data table but in different ways and a button that repopulates the data table.
<Window x:Class="BindingToDataTable.MainWindow"
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"
xmlns:local="clr-namespace:BindingToDataTable"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<RoutedCommand x:Key="RepopulateCommand"/>
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource RepopulateCommand}" Executed="RepopulateCommand_Executed"/>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Names}" SelectedValuePath="ID" SelectedValue="{Binding SelectedNameID}" DisplayMemberPath="Name"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding SelectedNameID}"/>
<ComboBox Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Names}" SelectedValuePath="ID" SelectedValue="{Binding SelectedNameIDString}" DisplayMemberPath="Name"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding SelectedNameIDString}"/>
<ComboBox Grid.Row="2" Grid.Column="0" ItemsSource="{Binding Names}" SelectedItem="{Binding SelectedName}" DisplayMemberPath="Name"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding SelectedName[ID]}"/>
<Button Grid.Row="3" Grid.Column="0" Content="Repopulate" Command="{StaticResource RepopulateCommand}" Height="20" Width="200"/>
</Grid>
</Window>
The code behind contains code to populate the data table and some properties.
using System;
public partial class MainWindow : Window, INotifyPropertyChanged
{
private DataTable _Names = null;
get { return _Names; }
private int _SelectedNameID;
get { return _SelectedNameID; }
private String _SelectedNameIDString;
get { return _SelectedNameIDString; }
private DataRowView _SelectedName;
get { return _SelectedName; }
public event PropertyChangedEventHandler PropertyChanged;
if (!Object.Equals(storage, value))
storage = value;
}
public MainWindow()
InitializeComponent();
private void InitializeNames()
Names = CreateNamesTable();
}
private DataTable CreateNamesTable()
DataTable n = new DataTable();
private void RepopulateCommand_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
InitializeNames();
}
}
When you first run the program everything looks fine. Don't play with the combo boxes yet.
Now click the [Repopulate] button. Oh, dear!
You can see the first combo box did not set it's selected item correctly and has a binding error as indicated by the red border. The other two combo boxes repopulated correctly. Let's try an experiment. Start the program again, select a different name in the first combo box, and click [Repopulate] again. It worked correctly. That's a clue.
- Always call PropertyChanged from SetProperty. Could be side-effects.
public event PropertyChangedEventHandler PropertyChanged; public void SetProperty<T>(ref T storage, T value, [CallerMemberName] string name = "") { storage = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name)); }
- Bind SelectedValue to a string (combo box 2). But if it's an integer, it should be stored as an integer.
- Bind SelectedItem instead of SelectedValue (combo box 3 - my preferred solution)
- Bind to a nullable integer (my second favorite solution)
private int? _SelectedNameID; public int? SelectedNameID { get { return _SelectedNameID; } set { SetProperty(ref _SelectedNameID, value); } }
- Set the bound integer to a non-zero value (-1) before repopulating the data table in InitializeNames. Guaranteed WTF during code review.
private void InitializeNames() { SelectedNameID = -1; Names = CreateNamesTable(); SelectedNameID = (int)Names.Rows[0]["ID"]; SelectedNameIDString = Names.Rows[0]["ID"].ToString(); SelectedName = Names.DefaultView[0]; }
No comments:
Post a Comment