0
votes

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.

1
The way you Clear and re-Add your observable collection worries me. It means that after the clear, the SelectedItem will be out of sync with the available items. Sometimes a Binding decides to disable updates when it can't produce proper results within a few attempts. - grek40
Ok, I'll check into it and report back. I'll rewrite it so that it wont assign a new ObservableCollection to QueryResults but will add/remove items to/from it. - Pizmovc
@grek40 Still doesn't work :/ - Pizmovc
Can you check, whether the collection- and propertychanges happen on the UI thread (something like Thread.CurrentThread == Dispatcher.Thread)? - grek40
Yes, both the collection change and the SelectedAddress changes occur on the UI thread. I compared the Windows thread and the current thread in the PropertyChange events and in the setter. - Pizmovc

1 Answers

0
votes

I figured it out.

Even though I was properly raising the PropertyChanged event, I did not indicate that my ViewModel implements the interface INotifyPropertyChanged, so that is why the change did not reflect on the UI.