如何在将表单控件绑定到类层次结构时避免使用繁琐的代码?

时间:2017-02-19 12:05:36

标签: c# .net wpf mvvm

我有一个与模型绑定的表单。有一个标准的基本模型和少数儿童模型(附加字段)。

在模型控件上方有一个单选按钮组,在选择其中一个时,会出现前面提到的附加字段(在本例中为句子字段)。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void sentencedPersonRadioButton_Checked(object sender, RoutedEventArgs e)
    {
        sentenceTextBox.Visibility = Visibility.Visible;
        DataContext = new SentencedPerson();
    }

    private void personRadioButton_Checked(object sender, RoutedEventArgs e)
    {
        sentenceTextBox.Visibility = Visibility.Hidden;
        DataContext = new Person();
    }
}

让我们说有一个人和SentencedPerson:

public class Person: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private String name;
    public String Name
    {
        get
        {
            return name;
        }

        set
        {
            if (value == name)
            {
                return;
            }

            name = value;
            OnPropertyChanged("Name");
        }
    }
}

public class SentencedPerson : Person
{
    private String sentence;
    public String Sentence
    {
        get
        {
            return sentence;
        }

        set
        {
            if (value == sentence)
            {
                return;
            }

            sentence = value;
            OnPropertyChanged("Sentence");
        }
    }
}

设计此类连接的正确方法是什么?添加新的'已检查'事件处理程序感觉很麻烦...我听说过MVVM模式,里面会有Person和SentencedPerson道具的某种PersonContext。但它并没有改变“检查”的需要。事件

也知道存在问题,因为公共字段的值是在设置新的DataContext之后。

2 个答案:

答案 0 :(得分:1)

这是一个相当广泛的问题,但我会给你一些指示。

MVVM是 推荐的设计模式,用于构建基于XAML的应用程序。

您可以使用" CurrentSelectedContent"创建一个视图模型类。类型为objectPerson的属性以及将enum绑定到的RadioButton属性。

有关更多信息以及如何使用MVVM将RadioButton绑定到enum源属性的示例,请参阅以下链接:

How to bind RadioButtons to an enum?

完成此操作后,您可以设置" CurrentSelectedContent"的值。属性基于视图模型中enum源属性的setter中的单选按钮选择:

private MyLovelyEnum _enum;
public MyLovelyEnum VeryLovelyEnum
{
    get
    {
        return _enum;
    }
    set
    {
        _enum = value;
        switch (value)
        {
            case MyLovelyEnum.Person:
                CurrentSelectedContent = new Person();
                break;
            //...
        }
        OnPropertyChanged("VeryLovelyEnum");

    }
}

确保" CurrentSelectedContent" property引发PropertyChanged事件,视图模型类实现INotifyPropertyChanged接口。

在视图中,您可以使用ContentControl并将其Content属性绑定到" CurrentSelectedContent"属性:

    <ContentControl Content="{Binding Content}">
        <ContentControl.ContentTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Name}" />
            </DataTemplate>
        </ContentControl.ContentTemplate>
    </ContentControl>

另外,请确保将视图的DataContext设置为视图模型的实例:

public MainWindow()
{
    InitializeComponent();
    DataContext = new ViewModel();
}

这是如何使用MVVM模式执行此操作的粗略思路。您不是在视图的代码隐藏中处理事件而是绑定到源属性,而不是显式设置特定UI元素的DataContext属性,而是将Content的{​​{1}}属性绑定到您在视图模型类中创建的对象。

希望有所帮助。

答案 1 :(得分:0)

你只需要一个型号:

public class Person : INotifyPropertyChanged
{
    string _name;
    public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }

    bool _isSentenced;
    public bool IsSentenced { get { return _isSentenced; } set { _isSentenced = value; RaisePropertyChanged("IsSentenced"); } }

    string _sentence;
    public string Sentence { get { return _sentence; } set { _sentence = value; RaisePropertyChanged("Sentence"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    } 
}

使用IsSentenced并将RadioButton绑定到它。另外,使用Visibility to Bool转换器检查显示Sentence字符串的TextBox的可见性到RadioButton的IsChecked属性。这是一个简单的例子:

<Window.Resources>
    <local:VisibilityToBoolConverter x:Key="VisibilityToBoolConverter"/>
</Window.Resources>
<ListBox DataContext="{Binding}" ItemsSource="{Binding Persons}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBox Text="{Binding Name}" />
                <RadioButton Content="Is sentenced to death" IsChecked="{Binding IsSentenced}" />
                <DockPanel  Visibility="{Binding IsSentenced , Converter={StaticResource VisibilityToBoolConverter}}">
                    <Label Content="Sentence:  "/>
                    <TextBlock Text="{Binding Sentence}" />
                </DockPanel>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate> 
</ListBox>

其中

public class VisibilityToBoolConverter : IValueConverter
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((bool)value == true)
            return Visibility.Visible;
        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((Visibility)value ==  Visibility.Visible)
            return true;
        return false;
    }
}

并且ViewModel是:

public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Person m1 = new Person() { Name = "person 1", IsSentenced = false, Sentence = "S S S" };
        Person m2 = new Person() { Name = "person 2", IsSentenced = false, Sentence = "B B B" };
        Person m3 = new Person() { Name = "person 3", IsSentenced = true, Sentence = "F F F" };
        _persons = new ObservableCollection<Person>() { m1, m2, m3 }; 

    } 
    ObservableCollection<Person> _persons;
    public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

你的主窗口应该设置DataContext:

    public MainWindow()
    { 
        PersonViewModel mv = new PersonViewModel();
        this.DataContext = mv;
        InitializeComponent(); 
    }

修改

如果一个人有很多状态,ComboBox是一个更自然的选择。你应该有一个描述状态的枚举:

public enum MyTypes
{
    None,
    IsA,
    IsB,
    IsC
}

并且Person应该有一个显示状态的peroperty:

public class Person : INotifyPropertyChanged
{
    MyTypes _thetype;
    public MyTypes TheType { get { return _thetype; } set { _thetype = value; RaisePropertyChanged("TheType"); } }

    string _name;
    public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

由于您需要将ComboBox的ItemsSource绑定到状态列表,因此可以调整ViewModel以获得这样的列表:

public class PersonViewModel : INotifyPropertyChanged
{
    public PersonViewModel()
    {
        Person m0 = new Person() { Name = "person 1", TheType = MyTypes.None };
        Person m1 = new Person() { Name = "person 1", TheType =  MyTypes.IsA };
        Person m2 = new Person() { Name = "person 2", TheType = MyTypes.IsB };
        Person m3 = new Person() { Name = "person 3", TheType = MyTypes.IsC };
        _persons = new ObservableCollection<Person>() { m0, m1, m2, m3 };

        _types = Enum.GetNames(typeof(MyTypes)).ToList();
    }

    List<string> _types;
    public List<string> Types { get { return _types; } } 


    ObservableCollection<Person> _persons;
    public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propname)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
    }
}

最后,观点:

<Window.Resources>
    <local:EnumToSentenceConverterx:Key="EnumToSentenceConverter"/>
    <local:NoneToCollapsedConverter x:Key="NoneToCollapsedConverter"/>
    <local:EnumToStringConverter x:Key="EnumToStringConverter"/>
</Window.Resources>
<ListBox Name="lb" DataContext="{Binding}" ItemsSource="{Binding Persons}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBox Text="{Binding Name}" />
                <ComboBox Name="cb" ItemsSource="{Binding ElementName=lb, Path=DataContext.Types}" SelectedValue="{Binding TheType, Mode=TwoWay, Converter={StaticResource EnumToStringConverter}}" />
                <DockPanel  Visibility="{Binding ElementName=cb, Path=SelectedValue, Converter={StaticResource NoneToCollapsedConverter}}">
                    <Label Content="Sentence:  " DockPanel.Dock="Left"/>
                    <TextBlock Text="{Binding TheType, Converter={StaticResource EnumToStringConverter}}" DockPanel.Dock="Right" VerticalAlignment="Center" />
                </DockPanel>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

请注意,您需要三个转换器。一个将Sentence部分的Visibility设置为Collapsed,类型为None:

public class NoneToCollapsedConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value.ToString() == "None")
            return Visibility.Collapsed;
        return Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

另外两个是自我描述性的:

public class EnumToStringConverter : IValueConverter
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Enum.Parse(typeof(MyTypes), value.ToString());
    }
}

public class EnumToSentenceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        switch ((MyTypes)value)
        {
            case MyTypes.IsA:

                break;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

希望它有所帮助。