0
votes

I have a ListBox with its ItemSource bound to an ObservableCollection. The ListBox has the following (minimalized) ItemTemplate:

<ListBox ItemsSource="{Binding SelectedDirectory.PluginValues}" HorizontalContentAlignment="Stretch">
    <ListBox.ItemTemplate>
         <DataTemplate>
              <Grid HorizontalAlignment="Stretch">                                   
                   <Grid Height="29" Margin="5" HorizontalAlignment="Stretch">
                       <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*" />
                            <ColumnDefinition Width="1*" />
                       </Grid.ColumnDefinitions>
                       <TextBox Grid.Column="0" Width="Auto" 
                                Text="{Binding Name, Mode=TwoWay
                                     , UpdateSourceTrigger=PropertyChanged}" />
                       <TextBox Grid.Column="1" Width="Auto" 
                                Text="{Binding Value, Mode=TwoWay
                                     , UpdateSourceTrigger=PropertyChanged}" />
                   </Grid>
              </Grid>
         </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

The binding option UpdateSourceTrigger=PropertyChanged causes the TextBoxes to loose the focus after each keypress to the surrounding ListBox. When I remove the option the focus is not lost, but the value in the TextBox is not immediately saved to the property. So when I enter a value and then raise a command (eg via save Button) the property is not updated. Only when I click somewhere else first and then raise the command the value is updated.

Edit

Simplified ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    private FbiDirectory selectedDirectory;

    public FbiDirectory SelectedDirectory
    {
        get
        {
            return this.selectedDirectory;
        }

        set
        {
            this.selectedDirectory = value;

            this.OnPropertyChanged("SelectedDirectory");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

FbiDirectory class (has nothing to do with the Federal Bureau of investigation):

public class FbiDirectory : INotifyPropertyChanged
{
    private ObservableCollection<PluginValue> pluginValues = new ObservableCollection<PluginValue>();

    public ObservableCollection<PluginValue> PluginValues
    {
        get
        {
            return this.pluginValues;
        }

        set
        {
            this.pluginValues = value;

            this.OnPropertyChanged("PluginValues");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

PluginValue class:

public class PluginValue : INotifyPropertyChanged
{
    private string name;
    private string value;

    public string Name
    {
        get => name;

        set
        {
            name = value;
            this.OnPropertyChanged("Name");
        }
    }
    public string Value
    {
        get => value;

        set
        {
            this.value = value;
            this.OnPropertyChanged("Value");
        }
    }

    public PluginValue(string name, string value)
    {
        this.Name = name;
        this.Value = value;
    }

    public PluginValue()
    {

    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}
1
I commented before your last edit. Now the question is clearer, but can't be answered, because we don't know what's happening when your view model changes. - Clemens
Do you actually need the selection behavior of a listbox or are you only interested in displaying a list of editable items? - grek40
@grek40 That is a really good question! And the answer is no. I just need a list of editable items in a scrollable content area. What control would be best to use for this? - Romano Zumbé
@grek40 I've put it into a StackPanel (Inside a ScrollViewer) with ItemsControl and the same DataTemplate. Unfortunately the behavior didn't change. Additionally, I realized, that I do need the selection behavior of the ListBox due to the fact, that I need the ability to delete the selected "value" - Romano Zumbé
Well if the behavior didn't change without a selector control, then you really need to show what's happening inside your ViewModel when the property changes. - grek40

1 Answers

0
votes

A simplified code for your problem may look like this:

public class MyViewModel
    {
        public ObservableCollection<ItemViewModel> Items { get; set; }

        public ICommand SaveCommand { get; }

        public MyViewModel()
        {
            SaveCommand = new RelayCommand(OnSaveCommand);
            Items = new ObservableCollection<ItemViewModel>();
            Items.Add(new ItemViewModel{Name = "test1", Value = "test1"});
            Items.Add(new ItemViewModel{Name = "test2", Value = "test2"});
        }

        private void OnSaveCommand()
        {
            var message = Items.Aggregate(new StringBuilder(),
                (builder, item) => builder.AppendLine($"{item.Name} {item.Value}"));
            message.AppendLine("Will be save");


            MessageBox.Show(message.ToString());
        }
    }

    public class ItemViewModel : NotifiableObject
    {
        private string _value;
        private string _name;

        public string Name
        {
            get => _name;
            set
            {
                OnPropertyChanged();
                _name = value;
            }
        }

        public string Value
        {
            get => _value;
            set
            {
                OnPropertyChanged();
                _value = value;
            }
        }
    }

    public class NotifiableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

With this view:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <ListBox ItemsSource="{Binding Items}" Grid.Row="0">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid HorizontalAlignment="Stretch">
                    <Grid Height="29" Margin="5" HorizontalAlignment="Stretch">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*" />
                            <ColumnDefinition Width="1*" />
                        </Grid.ColumnDefinitions>
                        <TextBox Grid.Column="0" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                        <TextBox Grid.Column="1" Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                    </Grid>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Grid.Row="1" Content="Save" Command="{Binding SaveCommand}"></Button>
</Grid>

Not really sure of what's wrong in your code but:

  • You should use a NotifableObject for the common RaisePropertyChanged behavior
  • I don't really understand why you use a FbiDirectory instead of directly put the pluginValues into your ViewModel?

Hope it helps.