WPF:取消数据绑定列表框中的用户选择?
如何取消数据绑定WPF列表框中的用户选择?源属性设置正确,但列表框选择不同步 我有一个MVVM应用程序,如果某些验证条件失败,它需要取消WPF列表框中的用户选择。验证由列表框中的选择触发,而不是由提交按钮触发WPF:取消数据绑定列表框中的用户选择?,wpf,mvvm,wpf-controls,Wpf,Mvvm,Wpf Controls,如何取消数据绑定WPF列表框中的用户选择?源属性设置正确,但列表框选择不同步 我有一个MVVM应用程序,如果某些验证条件失败,它需要取消WPF列表框中的用户选择。验证由列表框中的选择触发,而不是由提交按钮触发 ListBox.SelectedItem属性绑定到ViewModel.CurrentDocument属性。如果验证失败,视图模型属性的setter将退出,而不更改属性。因此,ListBox.SelectedItem绑定到的属性不会更改 如果发生这种情况,视图模型属性设置器会在退出之前引发P
ListBox.SelectedItem
属性绑定到ViewModel.CurrentDocument
属性。如果验证失败,视图模型属性的setter将退出,而不更改属性。因此,ListBox.SelectedItem
绑定到的属性不会更改
如果发生这种情况,视图模型属性设置器会在退出之前引发PropertyChanged事件,我认为这足以将ListBox重置为旧的选择。但这不起作用——列表框仍然显示新的用户选择。我需要覆盖该选择并使其与源属性恢复同步
为了防止不清楚,这里有一个例子:列表框有两个项,Document1和Document2;选择Document1。用户选择Document2,但Document1无法验证。ViewModel.CurrentDocument
属性仍设置为Document1,但列表框显示Document2已被选中。我需要将列表框选择返回到Document1
这是我的列表框绑定:
<ListBox
ItemsSource="{Binding Path=SearchResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=CurrentDocument, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
我确实尝试过使用从ViewModel(作为事件)到视图(订阅事件)的回调,以强制SelectedItem属性返回到旧的选择。我将旧文档与事件一起传递,它是正确的(旧的选择),但列表框选择不会更改回去
那么,如何使列表框选择与其SelectedItem
属性绑定到的视图模型属性恢复同步呢?谢谢你的帮助。-snip-
忘了我上面写的吧
我刚刚做了一个实验,事实上,只要你在setter中做任何更有趣的事情,SelectedItem就会失去同步。我想您需要等待setter返回,然后在ViewModel中异步更改该属性
使用MVVM Light helpers的快速脏工作解决方案(在我的简单项目中测试):
在setter中,还原为CurrentDocument的上一个值
var dp = DispatcherHelper.UIDispatcher;
if (dp != null)
dp.BeginInvoke(
(new Action(() => {
currentDocument = previousDocument;
RaisePropertyChanged("CurrentDocument");
})), DispatcherPriority.ContextIdle);
它基本上在UI线程上对属性更改进行排队,ContextIdle优先级将确保它将等待UI处于一致状态。在WPF中的事件处理程序中,似乎无法自由更改依赖项属性
不幸的是,它在视图模型和视图之间创建了耦合,这是一个丑陋的攻击
要使DispatcherHelper.UIDispatcher工作,您需要先执行DispatcherHelper.Initialize()。明白了!我将接受马约查的回答,因为他在回答下面的评论引导我找到了解决方案 下面是我所做的:我为代码隐藏中的列表框创建了一个
SelectionChanged
事件处理程序。是的,它很难看,但很管用。代码隐藏还包含一个模块级变量m\u OldSelectedIndex
,该变量初始化为-1。SelectionChanged
处理程序调用ViewModel的Validate()
方法,并获取一个布尔值,指示文档是否有效。如果文档有效,处理程序将m_OldSelectedIndex
设置为当前列表框。SelectedIndex
并退出。如果文档无效,处理程序会将列表框。SelectedIndex
重置为m\u OldSelectedIndex
。以下是事件处理程序的代码:
private void OnSearchResultsBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var viewModel = (MainViewModel) this.DataContext;
if (viewModel.Validate() == null)
{
m_OldSelectedIndex = SearchResultsBox.SelectedIndex;
}
else
{
SearchResultsBox.SelectedIndex = m_OldSelectedIndex;
}
}
请注意,此解决方案有一个技巧:必须使用SelectedIndex
属性;它不适用于SelectedItem
属性
谢谢你的帮助majocha,希望这能帮助其他人。像我一样,六个月后,当我忘记了这个解决方案时…对于这个问题上的未来绊脚石,这一页最终对我有效: 它适用于组合框,但也适用于列表框,因为在MVVM中,您并不真正关心调用setter的控件类型。正如作者所提到的,光荣的秘密是,实际上改变了潜在的价值,然后又改变回来。在单独的调度程序操作上运行此“撤消”也很重要。
private Person\u currentpersoncellable;
公众人士现任人员
{
得到
{
WriteLine(“Getting CurrentPersonCancellable”);
返回_currentPersonCellable;
}
设置
{
//存储当前值,以便
//如果需要的话,把它换回来。
var origValue=\u CurrentPersonCancelable;
//如果值没有更改,请不要执行任何操作。
如果(值==\u CurrentPersonCancelable)
返回;
//请注意,我们现在实际上更改了该值。
//这是必要的,因为WPF似乎要查询
//更改后的值。组合框
//喜欢知道值确实发生了变化。
_CurrentPersonCancellable=值;
如果(
MessageBox.Show(
“是否允许更改所选项目?”,
“继续”,
MessageBoxButton.YesNo
)!=MessageBoxResult。是
)
{
Debug.WriteLine(“选择已取消”);
//更改回该值,但请在
//UI已完成其当前上下文操作。
Application.Current.Dispatcher.BeginInvoke(
新操作(()=>
{
Debug.WriteLine(
“Dispatcher BeginInvoke”+
“设置CurrentPersonCancellable。”
);
//根据基础价值进行此操作
void DataContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
{
vm = DataContext as ViewModel;
if (vm != null)
vm.Items.CurrentChanged += Items_CurrentChanged;
}
private async void myListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var vm = DataContext as ViewModel; //for closure before await
if (vm != null)
{
if (myListView.SelectedIndex != vm.Items.CurrentPosition)
{
var changed = await vm.TrySetCurrentItemAsync(myListView.SelectedIndex);
if (!changed && vm == DataContext)
{
myListView.SelectedIndex = vm.Items.CurrentPosition; //reset index
}
}
}
}
void Items_CurrentChanged(object sender, EventArgs e)
{
var vm = DataContext as ViewModel;
if (vm != null)
myListView.SelectedIndex = vm.Items.CurrentPosition;
}
public async Task<bool> TrySetCurrentItemAsync(int newIndex)
{
DataModels.BatchItem newCurrentItem = null;
if (newIndex >= 0 && newIndex < Items.Count)
{
newCurrentItem = Items.GetItemAt(newIndex) as DataModels.BatchItem;
}
var closingItem = Items.CurrentItem as DataModels.BatchItem;
if (closingItem != null)
{
if (newCurrentItem != null && closingItem == newCurrentItem)
return true; //no-op change complete
var closed = await closingItem.TryCloseAsync();
if (!closed)
return false; //user said don't change
}
Items.MoveCurrentTo(newCurrentItem);
return true;
}
e.cancel = true;
//UserView is my ICollectionView that's bound to the listbox, that is currently changing
SelectedIndex = UserView.CurrentPosition;
//Use whatever similar notification method you use
NotifyPropertyChanged("SelectedIndex");
public class ListBoxSelectionChangedBehavior : Behavior<ListBox>
{
public static readonly DependencyProperty CommandProperty
= DependencyProperty.Register("Command",
typeof(ICommand),
typeof(ListBoxSelectionChangedBehavior),
new PropertyMetadata());
public static DependencyProperty CommandParameterProperty
= DependencyProperty.Register("CommandParameter",
typeof(object),
typeof(ListBoxSelectionChangedBehavior),
new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.SelectionChanged += ListBoxOnSelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= ListBoxOnSelectionChanged;
}
private void ListBoxOnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
Command.Execute(CommandParameter);
}
}
<ListBox x:Name="ListBox"
Margin="2,0,2,2"
ItemsSource="{Binding Taken}"
ItemContainerStyle="{StaticResource ContainerStyle}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
HorizontalContentAlignment="Stretch"
SelectedIndex="{Binding SelectedTaskIndex, Mode=TwoWay}">
<i:Interaction.Behaviors>
<b:ListBoxSelectionChangedBehavior Command="{Binding SelectionChangedCommand}"/>
</i:Interaction.Behaviors>
</ListBox>
public int SelectedTaskIndex
{
get { return _SelectedTaskIndex; }
set { SetProperty(ref _SelectedTaskIndex, value); }
}
private void SelectionChanged()
{
if (_OldSelectedTaskIndex >= 0 && _SelectedTaskIndex != _OldSelectedTaskIndex)
{
if (Taken[_OldSelectedTaskIndex].IsDirty)
{
SelectedTaskIndex = _OldSelectedTaskIndex;
}
}
else
{
_OldSelectedTaskIndex = _SelectedTaskIndex;
}
}
public RelayCommand SelectionChangedCommand { get; private set; }
SelectionChangedCommand = new RelayCommand(SelectionChanged);
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<ListBox
ItemsSource="{Binding Path=SearchResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=CurrentDocument, Mode=TwoWay, Delay=10}" />