thm*_*shd 5 data-binding silverlight mvvm silverlight-4.0
场景:在Silverlight 4 MVVM项目中,我们有一个ListBox
包含项目的控件,所选项目与ViewModel中的相应属性双向绑定.另一个控件(例如,我将其剥离为单个控件TextBox
)是绑定到所选项目内容的数据.该值应在休假/焦点丢失时更新.
问题:当TextBox
更改中的值并TextBox
通过按Tab键离开时,一切都按预期工作 - 值会更新.但是,如果用户单击其中的其他项ListBox
,则在触发setter的内容之前触发SelectedItem setter TextBox
,从而没有机会处理用户输入.
在向属性设置器添加断点时,您可以在调试器中看到在处理更新ListView
之前首先应用新选择TextBox
.
期望的行为:在用户选择另一个项目之前,我们需要知道当前选择的项目已被修改.不希望有一个自定义更新触发器,它会在每次按键时发出通知(我们知道这是可能的).
你能帮我吗?
代码(一个非常简单的例子):
视图模型
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ItemViewModel : ViewModelBase
{
private string _content;
public ItemViewModel(string initContent)
{
_content = initContent;
}
public string Content
{
get
{
return _content;
}
set
{
if (_content != value)
{
_content = value;
OnPropertyChanged("Content");
}
}
}
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<ItemViewModel> _items =
new ObservableCollection<ItemViewModel>();
private ItemViewModel _selectedViewModel;
public ObservableCollection<ItemViewModel> Items
{
get
{
return _items;
}
}
public ItemViewModel SelectedItem
{
get
{
return _selectedViewModel;
}
set
{
if (_selectedViewModel != value)
{
_selectedViewModel = value;
OnPropertyChanged("SelectedItem");
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
XAML
<Grid x:Name="LayoutRoot" Background="White">
<ListBox Height="100"
HorizontalAlignment="Left"
Margin="12,12,0,0"
VerticalAlignment="Top"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
DisplayMemberPath="Content"
Width="220" />
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="12,118,0,0"
Text="{Binding SelectedItem.Content, Mode=TwoWay}"
VerticalAlignment="Top"
Width="220" />
</Grid>
Run Code Online (Sandbox Code Playgroud)
XAML代码背后
public MvvmTestView()
{
InitializeComponent();
Loaded += new RoutedEventHandler(MvvmTestView_Loaded);
}
void MvvmTestView_Loaded(object sender, RoutedEventArgs e)
{
MainViewModel viewModel = new MainViewModel();
viewModel.Items.Add(new ItemViewModel("Hello StackOverflow"));
viewModel.Items.Add(new ItemViewModel("Thanks to Community"));
DataContext = viewModel;
}
Run Code Online (Sandbox Code Playgroud)
更新1 我提出了一个自行设计的解决方案供您查看,这可能是您接受的解决方案,我仍然希望鼓励您发表评论并提供您的提示.谢谢.
这是我们目前提出的一种解决方案。它的优点是将不同的任务分离到适当的层。例如,View 强制更新绑定,而 ViewModel 则告诉 View 这样做。另一个优点是它是同步处理的,例如,这允许在切换之前检查内容,并且调用堆栈保持不变,而不会引发“外部代码”(遍历Dispatcher
或什至DispatcherTimer
会这样做),这更有利于维护和流量控制。缺点是必须绑定和处理新事件(最后取消绑定。我仅出于示例原因提供匿名处理程序)。
到那里怎么走?
在 中ViewModelBase
,实现一个新ForceBindingUpdate
事件:
public abstract class ViewModelBase : INotifyPropertyChanged
{
// ----- leave everything from original code ------
public event EventHandler ForceBindingUpdate;
protected void OnForceBindingUpdate()
{
var handler = ForceBindingUpdate;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
Run Code Online (Sandbox Code Playgroud)
在 中MainViewModel
,更新属性的 setter SelectedItem
:
set // of SelectedItem Property
{
if (_selectedViewModel != value)
{
// Ensure Data Update - the new part
OnForceBindingUpdate();
// Old stuff
_selectedViewModel = value;
OnPropertyChanged("SelectedItem");
}
}
Run Code Online (Sandbox Code Playgroud)
更新MvvmTestView
隐藏代码以实现新事件:
void MvvmTestView_Loaded(object sender, RoutedEventArgs e)
{
// remains unchanged
Mvvm.MainViewModel viewModel = new Mvvm.MainViewModel();
viewModel.Items.Add(new Mvvm.ItemViewModel("Hello StackOverflow"));
viewModel.Items.Add(new Mvvm.ItemViewModel("Thanks to Community"));
// Ensure Data Update by rebinding the content property - the new part
viewModel.ForceBindingUpdate += (s, a) =>
{
var expr = ContentTextBox.GetBindingExpression(TextBox.TextProperty);
expr.UpdateSource();
};
// remains unchanged
DataContext = viewModel;
}
Run Code Online (Sandbox Code Playgroud)
最后但并非最不重要的一点是,最小的 XAML 更新:通过向XAML添加属性来TextBox
命名。x:Name="ContentTextBox"
TextBox
完毕。
实际上,我不知道这是否是最干净的解决方案,但它接近我们的想法。