WPF、将 CSV 多重绑定到复选框列表框以及 MVVM 最佳实践
我对整个 WPF 和 MVVM 想法还比较陌生,我正在寻找有关最佳实践的建议。我有一个可行的解决方案,但感觉我可能缺少一些可以简化整个事情的出色 XAML 语法。
我在数据库表中有一个字符串字段,该字段存储为 CSV,例如“CAT,DOG”。也许我应该在实体数据模型中将其作为多对多关系来完成,但这是一个不同的最佳实践讨论。
在我的 XAML 中,我在包含复选框的列表框中使用多重绑定。可能选择的范围是在运行时确定的,并且列表框使用数据模板生成复选框。这是 XAML:
<ListBox Grid.Column="3" Grid.Row="8" Grid.RowSpan="2" Name="brandedProductsListBox" Margin="3" ItemsSource="{Binding Source={StaticResource brandedProductLookup}}" IsSynchronizedWithCurrentItem="True" TabIndex="475">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="3" Content="{Binding Path=BrandedProductName}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource brandedProductToBoolean}">
<Binding Source="{StaticResource projectsView}" Path="BrandedProducts" />
<Binding Path="BrandedProductName" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
我使用转换器来检查适当的复选框。我尝试使用转换器的 ConvertBack 方法将布尔值转换为 CSV 字符串,但当我传递的只是布尔值时,我不知道如何访问 which BrandedProductName。这是转换器:
public class BrandedProductToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) {
return false;
}
else {
// The bindings passed in (in order) are: the BrandedProducts field for the current project,
// and the Branded Product represented by the current CheckBox.
string brandedProducts = value[0] as string;
string brandedProduct = value[1] as string;
return brandedProducts == null ? false : brandedProducts.Contains(brandedProduct);
}
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
}
因此,当选择一个实体时,Convert 会正确检查正确的复选框,但是当添加一个新实体时,我发现我可以使用复选框的 Checked 和 UnChecked 事件处理程序写回我的实体,如下所示
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts == null) {
project.BrandedProducts = (string)checkBox.Content;
}
else {
project.BrandedProducts += ", " + (string)checkBox.Content;
}
}
e.Handled = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts != null) {
project.BrandedProducts = project.BrandedProducts.Replace((string)checkBox.Content + ", ", "").Replace(", " + (string)checkBox.Content, "");
}
}
e.Handled = true;
}
:仍然和我在一起,问题是更好的方法是什么?这感觉有点像苹果和橘子,我使用转换器从实体生成视图,然后使用事件处理程序将视图更新/命令转换回实体。使用事件处理程序以这种方式修改我的 ViewModel 是否违反了 MVVM 的某些目标?
预先感谢您的任何建议, 射线
I'm relatively new to the whole WPF and MVVM idea and I'm looking for advice on a best practice. I have a solution that works but it feels like I might be missing some great XAML syntax that would simplify the whole thing.
I have a string field in a database table that is stored as a CSV, e.g. "CAT, DOG". Perhaps I should have done this as a many-to-many relationship in my entity data model, but that is a different best practice discussion.
In my XAML, I am using a multibinding on a ListBox that contains CheckBoxes. The domain of possible choices is determined at runtime and the ListBox generates CheckBoxes using a DataTemplate. Here's the XAML:
<ListBox Grid.Column="3" Grid.Row="8" Grid.RowSpan="2" Name="brandedProductsListBox" Margin="3" ItemsSource="{Binding Source={StaticResource brandedProductLookup}}" IsSynchronizedWithCurrentItem="True" TabIndex="475">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="3" Content="{Binding Path=BrandedProductName}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource brandedProductToBoolean}">
<Binding Source="{StaticResource projectsView}" Path="BrandedProducts" />
<Binding Path="BrandedProductName" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
I use a converter to check the appropriate CheckBoxes. I tried to get the ConvertBack method of the converter to turn a boolean into my CSV string, but I couldn't figure out how to get access which BrandedProductName when all I was passed was a boolean. Here's the converter:
public class BrandedProductToBooleanConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) {
return false;
}
else {
// The bindings passed in (in order) are: the BrandedProducts field for the current project,
// and the Branded Product represented by the current CheckBox.
string brandedProducts = value[0] as string;
string brandedProduct = value[1] as string;
return brandedProducts == null ? false : brandedProducts.Contains(brandedProduct);
}
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
}
So Convert properly checks the right CheckBoxes when an entity is selected, but when adding a new one I figured out I could use the Checked and UnChecked event handlers of the CheckBox to write back to my entity, like so:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts == null) {
project.BrandedProducts = (string)checkBox.Content;
}
else {
project.BrandedProducts += ", " + (string)checkBox.Content;
}
}
e.Handled = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
if (projectView.IsAddingNew) {
CheckBox checkBox = sender as CheckBox;
NewProject project = projectView.CurrentAddItem as NewProject;
if (project.BrandedProducts != null) {
project.BrandedProducts = project.BrandedProducts.Replace((string)checkBox.Content + ", ", "").Replace(", " + (string)checkBox.Content, "");
}
}
e.Handled = true;
}
If you're still with me, the question is what is a better way to do this? It feels a bit like apples and oranges with me using a converter to generate the view from the entity but then using event handlers to translate view updates/commands back to the entity. Does it violate some goal of MVVM to use event handlers to modify my ViewModel this way?
Thanks in advance for any suggestions,
Ray
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
射线,
我发现对于 WPF,如果您问这个问题,可能有更好的方法。有太多的选择(与软弱的 WinForms 相比)。
恕我直言,是的,它确实违反了 MVVM。您的代码隐藏中不应该有 ViewModel 代码(除了设置 ViewModel 绑定之外)。
您应该让您的事件执行由 ViewModel 公开的
ICommand
(即添加、删除)。请参阅 EventToCommand 这可能适用于您的CheckBox_Checked
和CheckBox_UnChecked
事件。-杰伯格
Ray,
I've found that with WPF, if you're asking this question, there probably is a better way. There are just so many options (compared to wimpy WinForms).
IMHO, yes, it does violate MVVM. You should have no ViewModel code (besides setting up a ViewModel Binding) in your code-behind.
You should have your events execute
ICommand
(s) which are exposed by your ViewModel (i.e. Add, Remove). See the EventToCommand which would probably apply to yourCheckBox_Checked
andCheckBox_UnChecked
events.-jberger