Dav*_*man 24 wpf mvvm wpf-controls
如何取消数据绑定WPF ListBox中的用户选择?source属性设置正确,但ListBox选择不同步.
我有一个MVVM应用程序,如果某些验证条件失败,需要取消WPF ListBox中的用户选择.验证由ListBox中的选择触发,而不是通过"提交"按钮触发.
该ListBox.SelectedItem
属性绑定到一个ViewModel.CurrentDocument
属性.如果验证失败,则视图模型属性的setter退出而不更改属性.因此,ListBox.SelectedItem
绑定的属性不会更改.
如果发生这种情况,视图模型属性setter会在它退出之前引发PropertyChanged事件,我认为这足以将ListBox重置回旧选择.但这不起作用 - ListBox仍然显示新的用户选择.我需要覆盖该选择并将其与source属性同步.
如果不清楚,这里有一个例子:ListBox有两个项目,Document1和Document2; 选择了Document1.用户选择Document2,但Document1无法验证.该ViewModel.CurrentDocument
属性仍设置为Document1,但ListBox显示已选择Document2.我需要将ListBox选择返回到Document1.
这是我的ListBox绑定:
<ListBox
ItemsSource="{Binding Path=SearchResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=CurrentDocument, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Run Code Online (Sandbox Code Playgroud)
我尝试使用从ViewModel(作为事件)到View(订阅事件)的回调,强制将SelectedItem属性返回到旧选择.我使用事件传递旧文档,它是正确的(旧选择),但ListBox选择不会更改回来.
那么,如何将ListBox选项与其SelectedItem
属性绑定的视图模型属性同步返回?谢谢你的帮助.
Aph*_*hex 38
对于这个问题的未来绊脚石,这个页面最终对我有用:http: //blog.alner.net/archive/2010/04/25/cancelling-selection-change-in-a-bound-wpf-combo- box.aspx
这是一个组合框,但适用于列表框就好了,因为在MVVM中你并不关心什么类型的控件调用setter.正如作者所提到的,光荣的秘密是实际改变潜在价值,然后再改变它.在单独的调度程序操作上运行此"撤消"也很重要.
private Person _CurrentPersonCancellable;
public Person CurrentPersonCancellable
{
get
{
Debug.WriteLine("Getting CurrentPersonCancellable.");
return _CurrentPersonCancellable;
}
set
{
// Store the current value so that we can
// change it back if needed.
var origValue = _CurrentPersonCancellable;
// If the value hasn't changed, don't do anything.
if (value == _CurrentPersonCancellable)
return;
// Note that we actually change the value for now.
// This is necessary because WPF seems to query the
// value after the change. The combo box
// likes to know that the value did change.
_CurrentPersonCancellable = value;
if (
MessageBox.Show(
"Allow change of selected item?",
"Continue",
MessageBoxButton.YesNo
) != MessageBoxResult.Yes
)
{
Debug.WriteLine("Selection Cancelled.");
// change the value back, but do so after the
// UI has finished it's current context operation.
Application.Current.Dispatcher.BeginInvoke(
new Action(() =>
{
Debug.WriteLine(
"Dispatcher BeginInvoke " +
"Setting CurrentPersonCancellable."
);
// Do this against the underlying value so
// that we don't invoke the cancellation question again.
_CurrentPersonCancellable = origValue;
OnPropertyChanged("CurrentPersonCancellable");
}),
DispatcherPriority.ContextIdle,
null
);
// Exit early.
return;
}
// Normal path. Selection applied.
// Raise PropertyChanged on the field.
Debug.WriteLine("Selection applied.");
OnPropertyChanged("CurrentPersonCancellable");
}
}
Run Code Online (Sandbox Code Playgroud)
注意:作者ContextIdle
用于DispatcherPriority
撤消更改的操作.虽然很好,但优先级低于Render
,这意味着更改将在UI中显示为所选项目暂时更改和更改.使用调度程序优先级Normal
或甚至Send
(最高优先级)优先显示更改.这就是我最终做的事情.有关DispatcherPriority
枚举的详细信息,请参见此处.
bwi*_*ing 12
在 .NET 4.5 中,他们将 Delay 字段添加到 Binding。如果您设置延迟,它将自动等待更新,因此 ViewModel 中不需要 Dispatcher。这适用于验证所有 Selector 元素,如 ListBox 和 ComboBox 的 SelectedItem 属性。延迟以毫秒为单位。
<ListBox
ItemsSource="{Binding Path=SearchResults, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=CurrentDocument, Mode=TwoWay, Delay=10}" />
Run Code Online (Sandbox Code Playgroud)
-snip-
好吧忘记我上面写的内容.
我刚做了一个实验,而且只要你在setter中做了更多花哨的事情,SelectedItem确实会失去同步.我想你需要等待setter返回,然后异步更改ViewModel中的属性.
使用MVVM Light助手快速而肮脏的工作解决方案(在我的简单项目中测试):在您的setter中,恢复到CurrentDocument的先前值
var dp = DispatcherHelper.UIDispatcher;
if (dp != null)
dp.BeginInvoke(
(new Action(() => {
currentDocument = previousDocument;
RaisePropertyChanged("CurrentDocument");
})), DispatcherPriority.ContextIdle);
Run Code Online (Sandbox Code Playgroud)
它基本上排队UI线程上的属性更改,ContextIdle优先级将确保它将等待UI处于一致状态.看起来你无法在WPF中的事件处理程序内自由更改依赖项属性.
不幸的是,它会在您的视图模型和视图之间创建耦合,这是一个丑陋的黑客攻击.
要使DispatcherHelper.UIDispatcher工作,首先需要执行DispatcherHelper.Initialize().
得到它了!我将接受majocha的回答,因为他在答案之下的评论让我得到了解决方案.
这是我做的wnat:我SelectionChanged
在代码隐藏中为ListBox 创建了一个事件处理程序.是的,这很难看,但它确实有效.代码隐藏还包含一个模块级变量,m_OldSelectedIndex
初始化为-1.该SelectionChanged
处理程序调用视图模型的Validate()
方法,并得到一个布尔值回指示文件是否有效.如果Document有效,则处理程序设置m_OldSelectedIndex
为当前ListBox.SelectedIndex
并退出.如果文档无效,则处理程序重置ListBox.SelectedIndex
为m_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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,此解决方案有一个技巧:您必须使用该SelectedIndex
属性; 它不适用于该SelectedItem
物业.
感谢您的帮助majocha,希望这将有助于其他人在路上.像我一样,六个月后,我忘记了这个解决方案......
如果您认真遵循 MVVM 并且不希望后面有任何代码,并且也不喜欢使用Dispatcher
,坦率地说这也不优雅,那么以下解决方案对我来说很有效,并且比大多数解决方案更优雅此处提供的解决方案。
它基于这样的概念:在后面的代码中,您可以使用事件停止选择SelectionChanged
。现在,如果是这种情况,为什么不为其创建一个行为,并将命令与事件关联起来SelectionChanged
。在视图模型中,您可以轻松记住先前选择的索引和当前选择的索引。诀窍是绑定到您的视图模型SelectedIndex
,并在选择发生变化时让该视图模型发生变化。但在选择真正发生更改后,SelectionChanged
事件会立即触发,现在通过命令通知您的视图模型。因为您记得之前选择的索引,所以可以验证它,如果不正确,则将所选索引移回原始值。
该行为的代码如下:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
在 XAML 中使用它:
<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>
Run Code Online (Sandbox Code Playgroud)
视图模型中适当的代码如下:
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; }
Run Code Online (Sandbox Code Playgroud)
在视图模型的构造函数中:
SelectionChangedCommand = new RelayCommand(SelectionChanged);
Run Code Online (Sandbox Code Playgroud)
RelayCommand
是 MVVM 光的一部分。如果你不知道的话谷歌一下。您需要参考
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Run Code Online (Sandbox Code Playgroud)
因此你需要参考System.Windows.Interactivity
.
归档时间: |
|
查看次数: |
23984 次 |
最近记录: |