C# 单视图、多视图模型-避免绑定错误?
有一个包含多个控件的单一视图(窗口),以简化:C# 单视图、多视图模型-避免绑定错误?,c#,wpf,mvvm,binding,C#,Wpf,Mvvm,Binding,有一个包含多个控件的单一视图(窗口),以简化: <!-- edit property A --> <TextBlock Text="A" ... /> <TextBox Text="{Binding Config.A}" ... /> <Button Command={Binding DoSometingWitA} ... /> <!-- edit property B --> <TextBox Text="{Binding C
<!-- edit property A -->
<TextBlock Text="A" ... />
<TextBox Text="{Binding Config.A}" ... />
<Button Command={Binding DoSometingWitA} ... />
<!-- edit property B -->
<TextBox Text="{Binding Config.B}" ... />
<!-- edit property C -->
<ComboBox Text="{Binding Config.C}" ... />
对于某些配置,属性可能或可能不存在。因此存在绑定错误
问题:有没有一种方法可以隐藏当前Config
对象中不存在的属性控件(这可以通过反射轻松实现)以及避免视图中出现绑定错误(这是实际问题,我不想重新发明PropertyGrid
也不想使用绑定错误)呢
例如,如果Config=new ConfigType1()
(仅具有A
属性),则视图将仅包含编辑属性A
的控件,编辑属性B
,C
等的控件应隐藏且不会导致绑定错误
如果有人愿意使用它,这里有一个测试用例 XAML: 最初存在缺少
B
的绑定错误,单击按钮后(ConfigB
将被分配),存在缺少a
的绑定错误
如何避免这些错误?可见性可以通过检查属性是否存在来控制(但仍然存在如何组织的问题)。要隐藏不显示的控件,只需将可见性属性(使用)绑定到主视图模型中的属性即可
<TextBox Text="{Binding Config.B}" Visibility="{Binding ShowConfigB, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"/>
但我不认为只有使用xaml才能阻止绑定错误。根据使用BindingOperations.SetBinding和BindingOperations.ClearBinding使用的配置类,可以在代码中添加或删除绑定
请记住,用户不会看到绑定错误。只有当它们由于某种原因影响性能时,我才会担心它们。您需要的是数据模板 工作样本:
public BaseConfig Config { get; set; }
有效(但糟糕)的解决方案是在代码隐藏中使用绑定:
XAML:
这里的关键是在更改配置时调用SetBindings()
,这将首先解除绑定(忽略DataContext
操作,它们只是因为缺少适当的ViewModel而出现在这里,请确保在解除绑定之前不要出现config
changed事件!)然后通过一些反射检查绑定代码隐藏,以避免绑定到不存在的属性以及控制可见性
在更好的解决方案出现之前,我将不得不使用这个解决方案。。我认为塔加普迪克的答案是正确的,但我认为一个样本可以更好地解释如何做 没有必要反省。这个想法是结合一个新的 让我们假设我们必须定义数据类型:
Data1
和Data2
。以下是他们的代码:
public class Data1
{
public string Name { get; set; }
public string Description { get; set; }
}
public class Data2
{
public string Alias { get; set; }
public Color Color { get; set; }
}
现在,我创建了一个简单的ViewModel:
public class ViewModel : PropertyChangedBase
{
private Data1 data1 = new Data1();
private Data2 data2 = new Data2();
private object current;
private RelayCommand switchCommand;
public ViewModel1()
{
switchCommand = new RelayCommand(() => Switch());
Current = data1;
}
public ICommand SwitchCommand
{
get
{
return switchCommand;
}
}
public IEnumerable<Color> Colors
{
get
{
List<Color> colors = new List<Color>();
colors.Add(System.Windows.Media.Colors.Red);
colors.Add(System.Windows.Media.Colors.Yellow);
colors.Add(System.Windows.Media.Colors.Green);
return colors;
}
}
private void Switch()
{
if (Current is Data1)
{
Current = data2;
return;
}
Current = data1;
}
public object Current
{
get
{
return current;
}
set
{
if (current != value)
{
current = value;
NotifyOfPropertyChange("Current");
}
}
}
}
如您所见,我为要在ContentPresenter
中处理的每个对象定义了一个DataTemplate
。我必须为每个DataTemplate
设置DataType
属性。这样,ContentPresenter中将自动使用适当的模板(取决于绑定到其DataContext的对象的类型)
您可以使用“切换”按钮在Data1
对象和Data2
对象之间切换。此外,如果查看VS的输出窗口,则不会看到关于绑定错误的消息
我希望我的样品能对你的问题有所帮助
编辑
我把我的答案的重点放在一个事实上,即使用DataTemplates,您不再有绑定错误。具有公共属性的对象和没有公共属性的对象之间没有太多区别
无论如何,让我们假设Data1
和Data2
都来自BaseData
类。下面是简单的代码:
public class BaseData
{
public bool IsValid { get; set; }
}
这样,IsValid
是Data1
和Data2
的公共属性。现在,您可以在两种可能的解决方案中进行选择:
IsValid
属性添加到两个“隐式”数据模板中BaseData
对象)并在“隐式”数据模板中重用它(优点:您必须编写更少的XAML-缺点:它可能会影响UI性能)<Window.Resources>
<CollectionViewSource x:Key="colors" Source="{Binding Path=Colors, Mode=OneTime}" />
<DataTemplate x:Key="{x:Type local:BaseData}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Is valid" VerticalAlignment="Center" />
<CheckBox IsChecked="{Binding IsValid}" Margin="5" Grid.Column="1" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Data1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Name" VerticalAlignment="Center" />
<TextBox Text="{Binding Name}" Grid.Column="1" Margin="5" />
<TextBlock Text="Description" Grid.Row="1" VerticalAlignment="Center" />
<TextBox Text="{Binding Description}" Grid.Column="1" Grid.Row="1" Margin="5" />
<ContentPresenter Grid.Row="2" Grid.ColumnSpan="2"
ContentTemplate="{StaticResource {x:Type local:BaseData}}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Data2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Alias" VerticalAlignment="Center" />
<TextBox Text="{Binding Alias}" Grid.Column="1" Margin="5" />
<TextBlock Text="Color" Grid.Row="1" VerticalAlignment="Center" />
<ComboBox Text="{Binding Color}" Grid.Column="1" Grid.Row="1" Margin="5"
ItemsSource="{Binding Source={StaticResource colors}}" />
<ContentPresenter Grid.Row="2" Grid.ColumnSpan="2"
ContentTemplate="{StaticResource {x:Type local:BaseData}}" />
</Grid>
</DataTemplate>
</Window.Resources>
是否通过反射向配置中添加属性?@tagaPdyk,否ConfigType1
只有一个属性A
,ConfigType2
只有一个属性B
,等等(请参见我的代码中的注释)。想法是使用相同的视图,其中对于每个可能的属性都有具有绑定的控件。当配置被更改时,不应该发生绑定错误(并且只有当前Config
具有的那些控件才应该可见)。。。。如果是ConfigType1,则使用datatemplate进行渲染,如果是ConfigType4,则使用同时包含A和B的datatemplate进行渲染,etc@Rachel,我也会这样做,但我不想重复代码。共有约100个设置,分为约20个配置,同一设置用于多个配置。我是g
private void Button_Click(object sender, RoutedEventArgs e)
{
// Config = new ConfigB() { B = "bbb" };
Config = new Config4() { ConfigA = (ConfigA) Config, ConfigB = new ConfigB { B = "bbb" } };
DataContext = null;
DataContext = this;
}
//…
// Rachel's point
public class Config4 : BaseConfig
{
public string A4 { get; set; }
public ConfigA ConfigA { get; set; }
public ConfigB ConfigB { get; set; }
}
<TextBox x:Name="textA" />
<TextBox x:Name="textB" />
public partial class MainWindow : Window
{
...
void SetBindings()
{
BindingOperations.ClearAllBindings(textA);
BindingOperations.ClearAllBindings(textB);
DataContext = null;
Bind(textA, "A");
Bind(textB, "B");
DataContext = this;
}
void Bind(UIElement element, string name)
{
if (Config?.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance) != null)
{
BindingOperations.SetBinding(element, TextBox.TextProperty, new Binding("Config." + name));
element.Visibility = Visibility.Visible;
}
else
element.Visibility = Visibility.Collapsed;
}
}
public class Data1
{
public string Name { get; set; }
public string Description { get; set; }
}
public class Data2
{
public string Alias { get; set; }
public Color Color { get; set; }
}
public class ViewModel : PropertyChangedBase
{
private Data1 data1 = new Data1();
private Data2 data2 = new Data2();
private object current;
private RelayCommand switchCommand;
public ViewModel1()
{
switchCommand = new RelayCommand(() => Switch());
Current = data1;
}
public ICommand SwitchCommand
{
get
{
return switchCommand;
}
}
public IEnumerable<Color> Colors
{
get
{
List<Color> colors = new List<Color>();
colors.Add(System.Windows.Media.Colors.Red);
colors.Add(System.Windows.Media.Colors.Yellow);
colors.Add(System.Windows.Media.Colors.Green);
return colors;
}
}
private void Switch()
{
if (Current is Data1)
{
Current = data2;
return;
}
Current = data1;
}
public object Current
{
get
{
return current;
}
set
{
if (current != value)
{
current = value;
NotifyOfPropertyChange("Current");
}
}
}
}
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="colors" Source="{Binding Path=Colors, Mode=OneTime}" />
<DataTemplate DataType="{x:Type local:Data1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Name" VerticalAlignment="Center" />
<TextBox Text="{Binding Name}" Grid.Column="1" Margin="5" />
<TextBlock Text="Description" Grid.Row="1" VerticalAlignment="Center" />
<TextBox Text="{Binding Description}" Grid.Column="1" Grid.Row="1" Margin="5" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Data2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Alias" VerticalAlignment="Center" />
<TextBox Text="{Binding Alias}" Grid.Column="1" Margin="5" />
<TextBlock Text="Color" Grid.Row="1" VerticalAlignment="Center" />
<ComboBox Text="{Binding Color}" Grid.Column="1" Grid.Row="1" Margin="5"
ItemsSource="{Binding Source={StaticResource colors}}" />
</Grid>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentPresenter Content="{Binding Path=Current}" />
<Button Content="Switch" Command="{Binding SwitchCommand}" Margin="30" />
</StackPanel>
</Window>
public class BaseData
{
public bool IsValid { get; set; }
}
<Window.Resources>
<CollectionViewSource x:Key="colors" Source="{Binding Path=Colors, Mode=OneTime}" />
<DataTemplate x:Key="{x:Type local:BaseData}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Is valid" VerticalAlignment="Center" />
<CheckBox IsChecked="{Binding IsValid}" Margin="5" Grid.Column="1" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Data1}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Name" VerticalAlignment="Center" />
<TextBox Text="{Binding Name}" Grid.Column="1" Margin="5" />
<TextBlock Text="Description" Grid.Row="1" VerticalAlignment="Center" />
<TextBox Text="{Binding Description}" Grid.Column="1" Grid.Row="1" Margin="5" />
<ContentPresenter Grid.Row="2" Grid.ColumnSpan="2"
ContentTemplate="{StaticResource {x:Type local:BaseData}}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Data2}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Alias" VerticalAlignment="Center" />
<TextBox Text="{Binding Alias}" Grid.Column="1" Margin="5" />
<TextBlock Text="Color" Grid.Row="1" VerticalAlignment="Center" />
<ComboBox Text="{Binding Color}" Grid.Column="1" Grid.Row="1" Margin="5"
ItemsSource="{Binding Source={StaticResource colors}}" />
<ContentPresenter Grid.Row="2" Grid.ColumnSpan="2"
ContentTemplate="{StaticResource {x:Type local:BaseData}}" />
</Grid>
</DataTemplate>
</Window.Resources>