This problem has been pestering me for the past few days. The window is a basic address entry, by querying the database and displaying the results.
User first enters the street (Bound to Query.Street), then city (Bound to Query.City) and then from the results list (DataGrid bound to QueryResults) selects the appropriate street.
Querying the DB is done in the PropertyChanged of Query object in the ViewModel and it works nicely. I bind the ObservableCollection<Location> QueryResults to DataGrid ItemsSource and when there is only one item left in QueryResults I assign it to Location SelectedAddress like this (this being the ViewModel):
this.PropertyChanged += (sender, e) =>
{
switch (e.PropertyName)
{
case "QueryResults":
// QueryResult contains only one result
if (this.QueryResults?.Count == 1)
{
// select that result
this.SelectedAddress = this.QueryResults.First();
}
break;
default:
break;
}
};
I subscribe to this event in the ViewModel constructor.
QueryResults in the ViewModel:
internal ObservableCollection<Location> _queryResults = new ObservableCollection<Location>();
public ObservableCollection<Location> QueryResults
{
get { return this._queryResults; }
set
{
this._queryResults.Clear();
if (value != null)
{
foreach (var item in value)
{
this._queryResults.Add(item);
}
}
OnPropertyChanged("QueryResults");
}
}
This is the SelectedAddress property:
internal Location _selectedAddress = new Location();
public Location SelectedAddress
{
get { return this._selectedAddress; }
set
{
this._selectedAddress = value;
OnPropertyChanged("SelectedAddress");
}
}
I then bind this.SelectedAddress to SelectedItem of the DataGrid, so that it would visually select it. DataGrid code:
<DataGrid Name="dgQueryResults"
MaxHeight="212.6"
RowHeaderWidth="0"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
IsReadOnly="True"
VerticalScrollBarVisibility="Hidden"
ItemsSource="{Binding QueryResults}"
SelectionMode="Single"
SelectionUnit="FullRow"
SelectedItem="{Binding SelectedAddress, Mode=TwoWay}"
SelectionChanged="dgQueryResults_SelectionChanged"
>
<DataGrid.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightGreen"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightGreen"/>
<Style TargetType="{x:Type DataGridCell}" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding Street}" IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="Street"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="*" Binding="{Binding City}" IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="City"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="*" Binding="{Binding Municipality}" IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="Municipality"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
dgQueryResults_SelectionChanged is there only to ScrollIntoView the selected row.
SelectedAddress is also bound to a panel which displays the selected address and a description (this is a standalone property in the ViewModel, that gets copied into SelectedAddress when it changes) that the user can enter, so that user can double check that they selected the correct address:
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="Selected address" FontWeight="DemiBold"/>
<DockPanel DockPanel.Dock="Bottom">
<TextBlock DockPanel.Dock="Top" Text="Floor, extra description:"/>
<TextBlock DockPanel.Dock="Bottom" Text="{Binding Description}"/>
</DockPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DataContext="{Binding SelectedAddress}">
<StackPanel>
<TextBlock Text="Street" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Street}" HorizontalAlignment="Left"/>
</StackPanel>
<StackPanel>
<TextBlock Text="House number" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding HouseNumber}" HorizontalAlignment="Left"/>
</StackPanel>
<StackPanel>
<TextBlock Text="City" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding City}" HorizontalAlignment="Left"/>
</StackPanel>
<StackPanel>
<TextBlock Text="Municipality" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Municipality}" HorizontalAlignment="Left"/>
</StackPanel>
</StackPanel>
</DockPanel>
The problem is that when there is only one item left in the DataGrid (or equally in QueryResults), I want to select it automatically (I check for this when QueryResults changes and if it contains only one item assign it to SelectedAddress), so that the DataGrid would update its selected item. This does not happen. SelectedAddress property in the ViewModel changes, the OnPropertyChanged("SelectedAddress"); is called, however DataGrid does not select the only row remaining, nor does the DockPanel update. However when I click on a row in the DataGrid, the SelectedAddress is updated and the DockPanel updates instantly.
Any help would be greatly appreciated!
Thanks in advance!
Edit: Resetting the windows DataContext when SelectedAddress changes works, but its a workaround that I'd rather not use.
Clearand re-Addyour observable collection worries me. It means that after the clear, theSelectedItemwill be out of sync with the available items. Sometimes aBindingdecides to disable updates when it can't produce proper results within a few attempts. - grek40ObservableCollectiontoQueryResultsbut will add/remove items to/from it. - PizmovcThread.CurrentThread == Dispatcher.Thread)? - grek40SelectedAddresschanges occur on the UI thread. I compared theWindows thread and the current thread in the PropertyChange events and in the setter. - Pizmovc