使用B替换每个TextBox的ViewModel中的A:一个属性:返回(可观察的)集合的一个属性

时间:2013-11-22 16:12:15

标签: c# wpf mvvm binding properties

让我们有两个枚举和一个模型类:

public enum A
{
    A1,
    A2,
    A3,
    A4
}
public enum B
{
    B1,
    B2,
    B3
}

// model class
public class MyModel
{
    private float[][] array;

    public MyModel()
    {
        array = new float[Enum.GetNames(typeof(A)).Length][];
        foreach (A a in EnumUtil.GetValues<A>())
        {
            array[(int) a] = new float[Enum.GetNames(typeof(B)).Length];
        }
    }

    public A EnumerationA { get; set; }
    public B EnumerationB { get; set; }
    public float this[A a, B b]
    {
        get
        {
            return array[(int) a][(int) b];
        }
        set
        {
            array[(int)a][(int)b] = value;
        }
    }
    public float[] ArraySlice
    {
        get
        {
            return array[(int) EnumerationA];
        }
    }
}


假设我们想要实现一个包含一组RadioButtons用于枚举A的视图,对于每个枚举B,我们想要一个TextBox。
更改单选按钮组中的单选按钮将允许在文本框中编辑不同的值集,更改后将显示先前输入的值。
enter image description here

我能想到的最简单的方法是:

public class MyViewModel : ViewModelBase
{
    private MyModel _myModel;

    public A EnumerationA
    {
        get
        {
            return _myModel.EnumerationA;
        }
        set
        {
            if (Enum.Equals(_myModel.EnumerationA, value) == false)
            {
                _myModel.EnumerationA = value;
                RaisePropertyChanged("EnumerationA");
            }
        }
    }

    public float ValueB1
    {
        get
        {
            return _myModel[EnumerationA, B.B1];
        }
        set
        {
            if (_myModel[EnumerationA, B.B1] != value)
            {
                _myModel[EnumerationA, B.B1] = value;
                RaisePropertyChanged("ValueB1");
            }
        }
    }
    // Analogously for ValueB2 and ValueB3 properties

}

观点:

<Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>      
      <RowDefinition Height="Auto"/>

    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto"/>
      <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>

  <Label Grid.Row="0" Grid.Column="0" Content="Enum A" />  
  <StackPanel Grid.Row="0" Grid.Column="1" Orientation="Vertical">
    <RadioButton IsChecked="{Binding Path=EnumerationA, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static model:A.A1}}" Content="A1" />
    <RadioButton IsChecked="{Binding Path=EnumerationA, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static model:A.A2}}" Content="A2" />
    <RadioButton IsChecked="{Binding Path=EnumerationA, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static model:A.A3}}" Content="A3" />
    <RadioButton IsChecked="{Binding Path=EnumerationA, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static model:A.A4}}" Content="A4 />
  </StackPanel>

  <Label Grid.Row="1" Grid.Column="0" Content="Float Values" />  
  <StackPanel Grid.Row="1" Grid.Column="1" Orientation="Vertical">
    <StackPanel Orientation="Horizontal"><Label Content="B1" /><TextBox Text="{Binding Path=ValueB1}" /></StackPanel>
    <StackPanel Orientation="Horizontal"><Label Content="B2" /><TextBox Text="{Binding Path=ValueB2}" /></StackPanel>
    <StackPanel Orientation="Horizontal"><Label Content="B3" /><TextBox Text="{Binding Path=ValueB3}" /></StackPanel>    
  </StackPanel>

 </Grid>

也就是说,这种方法为每个TextBox创建一个属性,并在radiobutton组选择发生变化时执行PropertyNotification。

问题:
是否可以以这样的方式执行此操作:ViewModel仅显示单个属性而不是三个属性(ValueB1,ValueB2,ValueB3): 这个单一属性将返回一个ObservableCollection&lt;&gt;或IList(或与索引选择类似的东西)?



我想到了一种可能的方法: 在VM中我们会有像

这样的东西
    public ObservableCollection<float> ArraySlice
    {
        get
        {
            return new ObservableCollection<float>(_myModel.ArraySlice);
        }

    }

在视图

<StackPanel Orientation="Horizontal">
<Label Content="B1" />
<TextBox Text="{Binding Path=WindSpeedAverage, Converter={StaticResource EnumToObservableCollectionConverter}, ConverterParameter={x:Static model:B.B1}}" />
</StackPanel>

这个EnumToObservableCollectionConverter - 一种方式很简单,它会将值转换为IList并简单地返回IList [参数],但这是我对此感到困惑的另一种方式... 即使我实现了ConvertBack方法,我只是简单地更新ObservableCollection而不是模型类中的值本身。

如何做到这一点?是否存在我缺少的常见模式

1 个答案:

答案 0 :(得分:0)

首先,我道歉:我在离开办公室前的最后一刻回答。

所以,这就是我如何解决你的问题,但还有很多其他的解决方案。这是一种粗略但最小化的方式来创建一个GUI(据我所知)。

让我们从viewmodels开始:你的案例看起来是一个简单的主 - 细节上下文,但也可以看作是一个分层上下文。

public class VM
{
    public VM()
    {
        this._aItems = new ObservableCollection<VMA>();
    }

    private ObservableCollection<VMA> _aItems;

    public Collection<VMA> AItems
    {
        get { return this._aItems; }
    }
}


public class VMA
{
    public VMA()
    {
        this._bItems = new ObservableCollection<VMB>();
    }

    public string Title { get; set; }

    private ObservableCollection<VMB> _bItems;

    public Collection<VMB> BItems
    {
        get { return this._bItems; }
    }
}


public class VMB : INotifyPropertyChanged
{
    public string Title { get; set; }

    private string _value;
    public string Value
    {
        get { return this._value; }
        set
        {
            if (this._value != value)
            {
                this._value = value;
                this.OnPropertyChanged("Value");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string name)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(
                this,
                new PropertyChangedEventArgs(name)
                );
        }
    }
}

请注意,“Value”属性的类型为“string”,尽管目标值应限制为浮点数。那是因为总是接受一个字符串,你可以实现自己的验证逻辑。相反,数字属性可能会导致异常,而输入的字符不允许作为数字。

上面的视图模型以下列方式填充(例如):

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

        var rnd = new Random();
        var vm = new VM();
        this.DataContext = vm;

        //enum "A"
        foreach (var a in Enum.GetNames(typeof(A)))
        {
            var vma = new VMA();
            vma.Title = a;
            vm.AItems.Add(vma);

            //enum "B"
            foreach (var b in Enum.GetNames(typeof(B)))
            {
                var vmb = new VMB();
                vmb.Title = b;
                vmb.Value = rnd.Next(1000).ToString();
                vma.BItems.Add(vmb);
            }
        }
    }
}

请注意,为了简单起见,我没有使用该模型,但您可以随时添加它。相反,我选择填充随机整数。

最后,这是XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" 
        Height="350" Width="525"
        >

    <Window.Resources>

        <DataTemplate x:Key="tplA">
            <RadioButton
                Content="{Binding Path=Title}"
                IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ListBoxItem}}"
                />
        </DataTemplate>

        <DataTemplate x:Key="tplB">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Title}" Width="30" />
                <TextBox Text="{Binding Path=Value}" Width="100" />
            </StackPanel>
        </DataTemplate>

    </Window.Resources>

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <ListBox
            ItemsSource="{Binding Path=AItems}"
            ItemTemplate="{StaticResource tplA}"
            Width="150"
            Height="200"
            Grid.Column="0"
            x:Name="L1"
            >
        </ListBox>

        <ListBox
            ItemsSource="{Binding Path=SelectedItem.BItems, ElementName=L1}"
            ItemTemplate="{StaticResource tplB}"
            Width="150"
            Height="200"
            Grid.Column="1"
            >
        </ListBox>
    </Grid>
</Window>

视觉效果如下图所示。

enter image description here