如何使用Mode = OneWay绑定并仅在保存时传播更改?

时间:2016-07-07 19:26:47

标签: c# wpf data-binding

问题

如何点击Save按钮而不是"失去焦点"?

,如何才能将注释的更改传播回列表?

只有在更改备注时才应启用Save按钮。

UI

示例应用程序如下所示:

UI

目前的行为是:

  • 点击便笺会将其文字放入TextBox;没关系。
  • TextBox失去焦点时,TextBox中已更改的文本将被写回列表(默认绑定行为);但我只希望在点击Save按钮时发生这种情况。
  • Save按钮始终处于激活状态,因为CanExecute(object parameter)尚未正确实施;它应该仅在TextBox文字与所选笔记的文字不同时才会被激活。

到目前为止我的研究

  • 选项1:某些互联网消息来源说要将不同的属性绑定到TextBox,并以编程方式检查它是否与SelectedItem的{​​{1}}不同。我希望除了现有的ListViewListOfNotes之外,还有一种方法可以不引入第三个属性。
  • 选项2:某些互联网消息来源建议配置SelectedNote,以便点击Mode=OneWay中的项目更新ListView,但不是相反。这听起来像我想要的解决方案,但我无法从代码示例中找出如何以编程方式引发事件,以便TextBox中的更改被写回{{1}单击TextBox按钮时。

我发现其他类似于我的Stackoverflow问题,但这些问题的答案帮助我解决了问题:

代码

此示例目前对焦点丢失进行双向绑定。我如何更改它以获得上述行为?

https://github.com/lernkurve/WpfBindingOneWayWithSaveButton

ListView

Save

MainWindow.xaml

<Window x:Class="WpfBindingOneWayWithSaveButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:wpfBindingOneWayWithSaveButton="clr-namespace:WpfBindingOneWayWithSaveButton"
        mc:Ignorable="d"
        Title="MainWindow" Height="188.636" Width="299.242">
    <Window.DataContext>
        <wpfBindingOneWayWithSaveButton:MainWindowsViewModel />
    </Window.DataContext>
    <Grid>
        <GroupBox Header="List of notes" HorizontalAlignment="Left" VerticalAlignment="Top" Height="112" Width="129" Margin="0,24,0,0">
            <ListView ItemsSource="{Binding ListOfNotes}" SelectedItem="{Binding SelectedNote}" DisplayMemberPath="Text" HorizontalAlignment="Left" Height="79" VerticalAlignment="Top" Width="119" Margin="0,10,-2,0"/>
        </GroupBox>
        <GroupBox Header="Change selected note" HorizontalAlignment="Left" Margin="134,24,0,0" VerticalAlignment="Top" Height="112" Width="151">
            <Grid HorizontalAlignment="Left" Height="89" Margin="0,0,-2,0" VerticalAlignment="Top" Width="141">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="40*"/>
                    <ColumnDefinition Width="101*"/>
                </Grid.ColumnDefinitions>
                <TextBox Text="{Binding SelectedNote.Text}" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="121" Margin="10,7,0,0" Grid.ColumnSpan="2"/>
                <Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="121" Margin="10,35,0,0" Grid.ColumnSpan="2"/>
            </Grid>
        </GroupBox>
    </Grid>
</Window>

MainWindowsViewModel.cs

using System.Collections.ObjectModel;
using System.Windows.Input;

namespace WpfBindingOneWayWithSaveButton
{
    public class MainWindowsViewModel
    {
        public ObservableCollection<Note> ListOfNotes { get; set; }

        public Note SelectedNote { get; set; }

        public ICommand SaveCommand { get; set; }

        public MainWindowsViewModel()
        {
            ListOfNotes = new ObservableCollection<Note>
            {
                new Note { Text = "Note 1" },
                new Note { Text = "Note 2" }
            };
            SaveCommand = new SaveCommand(this);
        }
    }
}

SaveCommand.cs

using System;
using System.Windows.Input;

namespace WpfBindingOneWayWithSaveButton
{
    public class SaveCommand : ICommand
    {
        private MainWindowsViewModel vm;

        public SaveCommand(MainWindowsViewModel vm)
        {
            this.vm = vm;
        }

        public bool CanExecute(object parameter)
        {
            // What should go here?
            return true;

            // Pseudo code
            // return (is the TextBox text different from the original note text)
        }

        public void Execute(object parameter)
        {
            // What should go here?

            // Pseudo code
            // Let WPF know that the TextBox text has changed
            // Invoke the binding so it propagates the TextBox text back to the list
        }

        public event EventHandler CanExecuteChanged;
    }
}

3 个答案:

答案 0 :(得分:3)

将文本绑定到CommandParameter的{​​{1}},以便将其传递给Save方法进行更新。

SaveButton

 <TextBox x:Name="NoteTextBox" Text="{Binding SelectedNote.Text, Mode=OneTime}" ../>
 <Button Command="{Binding SaveCommand}" 
         CommandParameter="{Binding ElementName=NoteTextBox, Path=Text}", 
         Content="Save" />

答案 1 :(得分:2)

选项一是最容易实现的,您需要克隆Note对象并将其设置为单独的属性。

在您的xaml中,将列表视图更改为以下内容,以便现在绑定SelectedIndex而不是SelectedItem

<ListView ItemsSource="{Binding ListOfNotes}" SelectedIndex="{Binding SelectedIndex}" DisplayMemberPath="Text" ...

并将TextBox更改为以下内容,以便在您键入时更新绑定

<TextBox Text="{Binding Path=SelectedNote.Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" ...

在Note.cs中,我们添加了Clone()方法。

public class Note : INotifyPropertyChanged
{

    public Note Clone()
    {
        return new Note()
        {
            Text = this.Text
        };
    }

    //... The rest stays the same
}

在MainWindowsViewModel.cs中,我们为SelectedIndex添加新属性,并在检测到索引已更改时克隆对象。我们还需要添加INotifyPropertyChanged,以便在执行SelectedNote

时我们可以从代码隐藏中更新Clone()
public class MainWindowsViewModel : INotifyPropertyChanged
{
    private int _selectedIndex = -1;
    private Note _selectedNote;

    public int SelectedIndex
    {
        get { return _selectedIndex; }
        set
        {
            if (_selectedIndex.Equals(value))
                return;

            _selectedIndex = value;

            CloneSelectedNote();
        }
    }

    private void CloneSelectedNote()
    {
        if (SelectedIndex >= 0)
        {
            SelectedNote = ListOfNotes[SelectedIndex].Clone();
        }
        else
        {
            SelectedNote = null;
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

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

    public Note SelectedNote
    {
        get { return _selectedNote; }
        set
        {
            if(Equals(_selectedNote, value))
                return;

            _selectedNote = value;
            OnPropertyChanged();
        }
    }

    //... The rest stays the same
}

在SaveCommand.cs中,我们为CanExecute添加逻辑并将订阅添加到CommandManager.RequerySuggested,这会在任何绑定更改时自动重新查询CanExecute。这可能有点不合时宜,如果您希望公开公开RaiseCanExecuteChanged(),但MainWindowsViewModelvm.SelectedIndex更改后,vm.SelectedNote.Text有责任致电public class SaveCommand : ICommand { private MainWindowsViewModel vm; public SaveCommand(MainWindowsViewModel vm) { this.vm = vm; } public bool CanExecute(object parameter) { if (vm.SelectedIndex < 0 || vm.SelectedNote == null) return false; return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text; } public void Execute(object parameter) { vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } } 。 / p>

CommandManager

更新:以下是不使用public class MainWindowsViewModel : INotifyPropertyChanged { private int _selectedIndex = -1; private Note _selectedNote; public int SelectedIndex { get { return _selectedIndex; } set { if (_selectedIndex.Equals(value)) return; _selectedIndex = value; CloneSelectedNote(); RecheckSaveCommand(); } } private void CloneSelectedNote() { if (SelectedIndex >= 0) { SelectedNote = ListOfNotes[SelectedIndex].Clone(); } else { SelectedNote = null; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public Note SelectedNote { get { return _selectedNote; } set { if(Equals(_selectedNote, value)) return; if (_selectedNote != null) { PropertyChangedEventManager.RemoveHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text)); } _selectedNote = value; if (_selectedNote != null) { PropertyChangedEventManager.AddHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text)); } OnPropertyChanged(); } } private void SelectedNoteTextChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { RecheckSaveCommand(); } private void RecheckSaveCommand() { var command = this.SaveCommand as WpfBindingOneWayWithSaveButton.SaveCommand; //"this." and "WpfBindingOneWayWithSaveButton." are not necessary but I wanted to be explicit. if (command != null) { command.RaiseCanExecuteChanged(); } } //... }

的更新版本

MainWindowsViewModel.cs

public class SaveCommand : ICommand
{
    private MainWindowsViewModel vm;

    public SaveCommand(MainWindowsViewModel vm)
    {
        this.vm = vm;
    }

    public bool CanExecute(object parameter)
    {
        if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
            return false;

        return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
    }

    public void Execute(object parameter)
    {
        vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

SaveCommand.cs

@autobind

答案 2 :(得分:0)

您不应使用OneWay,而应使用价值UpdateSourceTrigger的{​​{1}}。 BindingGroups可以为你做这个,这是一个简单的例子:

Explicit
<!-- For change observation -->
<TextBlock Text="{Binding Text}"></TextBlock>

<StackPanel>
    <StackPanel.BindingGroup>
        <BindingGroup x:Name="EditGroup"></BindingGroup>
    </StackPanel.BindingGroup>
    <TextBox Text="{Binding Text}"></TextBox>

    <Button>
        <Button.Command>
            <local:CommitGroupCommand BindingGroup="{x:Reference EditGroup}"/>
        </Button.Command>
        Save
    </Button>
</StackPanel>

(您可以在绑定中添加验证规则,该规则要求值不同,并将其用于public class CommitGroupCommand : ICommand { public BindingGroup BindingGroup { get; set; } public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { BindingGroup.UpdateSources(); } } 实现。)

使用此方法可以直接绑定到要编辑的对象,因此您无需先复制值。