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.