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>