繁体   English   中英

单视图,多个视图模型-避免绑定错误?

[英]Single View, multiple ViewModels - avoid binding errors?

具有多个控件的单个视图(窗口)可以简化:

<!-- 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}" ... />

该视图用于显示和编辑多个配置:

public class ViewModel: INotifyPropertyChanged
{
    public BaseConfig Config {get {...} set {...}}
}

public class ConfigType1: BaseConfig { ... } // only has A
public class ConfigType2: BaseConfig { ... } // only has B
public class ConfigType3: BaseConfig { ... } // only has C
public class ConfigType4: BaseConfig { ... } // has A and B
public class ConfigType5: BaseConfig { ... } // has A and C

某些配置的属性可能存在或存在。 结果是存在绑定错误。

问题:有没有一种方法可以隐藏控件,即哪些属性在当前Config对象中不存在(可以通过反射轻松完成)以及避免发生绑定错误(这是实际的问题,我不想-invent PropertyGrid还是我不想在视图中使用)?

例如,如果Config = new ConfigType1() (仅具有A属性),则View将仅包含用于编辑属性A控件,用于编辑属性BC等的控件应被隐藏,并且不会引起绑定错误。


如果有人愿意玩的话,这是一个测试用例。

XAML:

<TextBox Text="{Binding Config.A}" Visibility="Collapsed"/>
<TextBox Text="{Binding Config.B}" Visibility="Hidden"/>
<Button VerticalAlignment="Bottom"
        Content="..."
        Click="Button_Click" />

CS:

public partial class MainWindow : Window
{
    public class BaseConfig { }

    public class ConfigA : BaseConfig
    {
        public string A { get; set; }
    }

    public class ConfigB : BaseConfig
    {
        public string B { get; set; }
    }

    public BaseConfig Config { get; private set; }

    public MainWindow()
    {
        InitializeComponent();
        Config = new ConfigA() { A = "aaa" };
        DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Config = new ConfigB() { B = "bbb" };
        DataContext = null;
        DataContext = this;
    }
}

最初存在缺少B的绑定错误,单击按钮(将分配ConfigB )后,存在缺少A的绑定错误。

如何避免这些错误? 可以通过反射检查属性是否存在来控制可见性(但是仍然存在如何组织属性的问题)。

您需要的是DataTemplate。

工作样本:

 public BaseConfig Config { get; set; }
 <Window.Resources>
    <DataTemplate DataType="{x:Type o:ConfigA}">
        <!--
          You can add here any control you wish applicable to ConfigA.
          Say, a textbox can do.  
         -->
        <TextBlock Text="{Binding A}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type o:ConfigB}">
        <TextBlock Text="{Binding B}"/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type o:ConfigType10000000000}">
        <superComplicatedControl:UniqueControl ProprietaryProperty="{Binding CustomProperty}"/>
    </DataTemplate>
    <!--  Rachel's point  -->
    <DataTemplate DataType="{x:Type o:Config4}">
        <StackPanel>
           <ContentControl Content="{Binding ConfigA}"/>
           <ContentControl Content="{Binding ConfigB}"/>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<Grid>
    <StackPanel>
         <ContentControl Content="{Binding Config}" />
         <Button VerticalAlignment="Bottom" Content="woosh" Click="Button_Click" />
    </StackPanel>
 </Grid>
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; } 
}

我认为tagaPdyk的答案是正确的,但我认为一个样本可以更好地解释该怎么做。

不需要反思。 这个想法是将隐式DataTemplatesContentPresenter结合在一起。

假设我们必须要数据类型: Data1Data2 这里是他们的代码:

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");
            }
        }
    }
}

其中PropertyChangedBaseINotifyPropertyChanged的基本实现类。

现在最重要的-对于这个问题-部分,即XAML

<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>

如您所见,我为要在ContentPresenter处理的每个对象定义了一个DataTemplate 我必须为每个DataTemplate设置DataType属性。 这样,适当的模板将自动在ContentPresenter内部使用(取决于绑定到其DataContext的对象的类型)。

您可以使用“切换”按钮在Data1对象和Data2对象之间切换。 此外,如果您查看VS的“输出”窗口,将不会看到有关绑定错误的消息。

希望我的样本可以帮助您解决问题。

编辑

我将回答的重点放在以下事实上:使用DataTemplates,您不再有绑定错误。 具有共同属性的对象与没有共同属性的对象之间没有太多区别。

无论如何,我们假设Data1Data2都从BaseData类派生。 这里是它的简单代码:

public class BaseData
{
    public bool IsValid { get; set; }
}

这样, IsValidData1Data2的公共属性。 现在,您可以在两种可能的解决方案之间进行选择:

  1. 您将IsValid属性添加到两个“隐式”数据模板。
  2. 您创建一个“基本” DataTemplate(用于BaseData对象),然后在“隐式” DataTemplates中重用它(例如:您必须编写更少的XAML-缺点:它可能会影响UI性能)

关于第二个解决方案,您的DataTemplates将变为:

<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>

可行的(但糟糕的)解决方案是在代码隐藏中使用绑定:

XAML:

<TextBox x:Name="textA" />
<TextBox x:Name="textB" />

CS:

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;
    }
}

此处的关键是在每次更改配置时调用SetBindings() ,这将首先取消绑定 (忽略DataContext操作,仅由于缺少适当的ViewModel而在此处,请确保在取消绑定之前不要引发Config change事件!),然后绑定代码-在后面进行一些反射检查,以避免绑定到不存在的属性以及控件可见性。

我将不得不使用此解决方案,直到有更好的解决方案出现为止。

要隐藏您不希望显示的控件,只需将Visibility属性(使用BooleanToVisibilityConverter )绑定到主视图模型中的属性即可。

<TextBox Text="{Binding Config.B}" Visibility="{Binding ShowConfigB, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"/>

并在ViewModel中

public bool ShowConfigB
{
    get
    {
      return (Config.GetType() == typeof(ConfigType2));
    }
}

我不认为您只能通过xaml停止绑定错误。 您可以根据使用BindingOperations.SetBinding和BindingOperations.ClearBinding的配置类在代码中添加或删除绑定。

请记住,用户看不到绑定错误。 如果它们由于某种原因影响性能,我只会担心它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM