【问题标题】:Control looses focus on UpdateSourceTrigger = PropertyChanged控制失去对 UpdateSourceTrigger = PropertyChanged 的​​关注
【发布时间】:2017-06-30 07:45:05
【问题描述】:

我有一个ListBox,它的ItemSource 绑定到一个ObservableCollectionListBox 具有以下(最小化)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>

绑定选项UpdateSourceTrigger=PropertyChanged 会导致TextBoxes 在每次按键后松开焦点到周围的ListBox。当我删除该选项时,焦点不会丢失,但TextBox 中的值不会立即保存到属性中。因此,当我输入一个值然后发出一个命令(例如,通过保存Button)时,该属性不会更新。只有当我先点击其他地方然后提出命令时,值才会更新。

编辑

简化视图模型:

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 类(与联邦调查局无关):

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 类:

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));
    }
}

【问题讨论】:

  • 我在您上次编辑之前发表了评论。现在问题更清楚了,但无法回答,因为我们不知道当您的视图模型发生变化时会发生什么。
  • 您真的需要列表框的选择行为还是只对显示可编辑项目列表感兴趣?
  • @grek40 这是一个非常好的问题!答案是否定的。我只需要一个可滚动内容区域中的可编辑项目列表。为此最好使用什么控件?
  • @grek40 我已将其放入StackPanel(在ScrollViewer 内)与ItemsControl 和相同的DataTemplate。不幸的是,行为没有改变。此外,我意识到,我确实需要ListBox 的选择行为,因为我需要能够删除选定的“值”
  • 好吧,如果没有选择器控件的情况下行为没有改变,那么当属性更改时,您确实需要显示 ViewModel 内部发生的情况。

标签: c# wpf xaml data-binding


【解决方案1】:

您的问题的简化代码可能如下所示:

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));
        }
    }

有了这个观点:

<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>

不太确定您的代码有什么问题,但是:

  • 您应该为常见的 RaisePropertyChanged 行为使用 NotifableObject
  • 我真的不明白为什么您使用 FbiDirectory 而不是直接将 pluginValues 放入您的 ViewModel 中?

希望对你有帮助。

【讨论】:

  • 您的回答只是建议将我的 INotifyPropertyChanged 实现更改为基类以从中派生我的 ViewModel?这对我没有任何帮助。在 FbiDirectory 类中有一堆其他属性,这些属性只对目录感兴趣。我的 ViewModel 包含许多与 PluginValues 集合没有任何关系的其他对象。只是关注点分离。
猜你喜欢
  • 2011-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多