如何在ViewModel中使用数组?

时间:2018-10-22 01:42:19

标签: c# asp.net asp.net-mvc xamarin xamarin.forms

我的代码现在看起来像这样,每条消息有两行代码。该代码有效,但是如果我有30条消息,每条消息都可以给它们赋值,那么我将需要60行代码来声明所有内容:

string _msg1;
string _msg2;
public string Msg1 { get => _msg1; set => SetProperty(ref _msg1, value); }
public string Msg2 { get => _msg2; set => SetProperty(ref _msg2, value); }

在C#中,我分配给这些:

vm.Msg1 = "A";
vm.Msg2 = "B"; 

,在XAML中,我将我的文本绑定到Msg1,将另一个文本绑定到Msg2

有人可以告诉我如何/是否可以对数组执行此操作,以便我可以这样分配,并希望数组的分配可以只用两行完成,而不是每条消息的两行:

vm.Msg[0] = "A";
vm.Msg[1] = "B";

供参考:

public class ObservableObject : INotifyPropertyChanged
{

    protected virtual bool SetProperty<T>(
        ref T backingStore, T value,
        [CallerMemberName]string propertyName = "",
        Action onChanged = null)
    {
        if (EqualityComparer<T>.Default.Equals(backingStore, value))
            return false;

        backingStore = value;
        onChanged?.Invoke();
        OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

}

5 个答案:

答案 0 :(得分:7)

数组不会引发属性更改事件。您需要使用一个ObservableCollection,它可以在更改集合后引发一个事件。但是,当集合内的对象更改其值时,这不会引发事件。您需要将对象(在这种情况下为字符串)包装为可以引发属性更改事件的类型。

类似以下的方法将起作用:

    public class BindableValue<T> : INotifyPropertyChanged
    {
        private T _value;
        public T Value
        { get => _value; set => SetProperty(ref _value, value); }
        // INotifyPropertyChanged and SetProperty implementation goes here
    }

    private ObservableCollection<BindableValue<string>> _msg;
    public ObservableCollection<BindableValue<string>> Msg
    { get => _msg; set => SetProperty(ref _msg1, value); }

您将绑定到Msg[0].ValueMsg[1].Value等,

答案 1 :(得分:6)

您可以使用indexing that supports property change notification创建一个简单的包装器类。

例如:

public class Messages : ObservableObject
{
    readonly IDictionary<int, string> _messages = new Dictionary<int, string>();

    [IndexerName("Item")] //not exactly needed as this is the default
    public string this[int index]
    {
        get
        {
            if (_messages.ContainsKey(index))
                return _messages[index];

//Uncomment this if you want exceptions for bad indexes
//#if DEBUG
//          throw new IndexOutOfRangeException();
//#else
            return null; //RELEASE: don't throw exception
//#endif
        }

        set
        {
            _messages[index] = value;
            OnPropertyChanged("Item[" + index + "]");
        }
    }
}

然后,在视图模型中创建一个属性,如下所示:

private Messages _msg;
public Messages Msg
{
    get { return _msg ?? (_msg = new Messages()); }
    set { SetProperty(ref _msg, value); }
}

现在您可以将值设置或更新为:

vm.Msg[0] = "A";
vm.Msg[1] = "B";

XAML中的绑定将与:

<Label Text="{Binding Msg[0]}" />
<Label Text="{Binding Msg[1]}" />

示例使用代码

XAML

<StackLayout Margin="20">
    <Label Text="{Binding Msg[0]}" />
    <Label Text="{Binding Msg[1]}" />
    <Label Text="{Binding Msg[2]}" />
    <Label Text="{Binding Msg[3]}" />
    <Label Text="{Binding Msg[4]}" />

    <Button Text="Trigger update" Command="{Binding UpdateMessage}" />
</StackLayout>

代码隐藏的视图模型

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        var viewModel = new MainViewModel();

        viewModel.Msg[0] = "Original message 1";
        viewModel.Msg[1] = "Original message 2";
        viewModel.Msg[2] = "Original message 3";
        viewModel.Msg[3] = "Original message 4";
        viewModel.Msg[4] = "Original message 5";

        BindingContext = viewModel;
    }
}

public class MainViewModel : ObservableObject
{
    private Messages _msg;
    public Messages Msg
    {
        get { return _msg ?? (_msg = new Messages()); }
        set { SetProperty(ref _msg, value); }
    }

    public ICommand UpdateMessage => new Command(() =>
           {
               Msg[2] = "New message 3";
               Msg[0] = "New message 1";
           });
}

enter image description here

答案 2 :(得分:3)

我假设您给定的示例正在按预期方式运行和运行(Atleast有2个项目)

查看 代码

  

假设您希望将所有30条消息显示为列表。

<ListView ItemsSource="{Binding MessagesArray}"/>
  

还应该正确设置DataContext,如果需要帮助,请在下面注释

查看模型代码。

  

我们使用的是 ObservableCollection 而不是数组。由于纯数组不支持适当的绑定功能。

private ObservableCollection<string> _messagesArray;

public ObservableCollection<string> MessagesArray  
{  
    get { return _messagesArray; }  
    set { SetProperty(ref _messagesArray, value); }  
}  

分配值

MessagesArray = new ObservableCollection<string>();
vm.MessagesArray.Add("A");
vm.MessagesArray.Add("B");

在分配代码MessagesArray = new ObservableCollection<string>();中分配一个ObservableCollection的{​​{1}}新对象

  

如果您是String的新手,可以将其视为ObservableCollection的包装,但实际上并非如此

string[]方法将告诉SetProperty视图新集合已到达,因此UI将重新呈现该列表。

当您调用XAML内部的vm.MessagesArray.Add("B");内部逻辑时,将告诉Add视图。新项目已添加到XAML中,因此视图可以重新呈现{{ 1}}和新项目。

更新2018年10月27日

您可以使用以下任何一种方法来创建自己的数组:任何。 (不是全部)

ObservableCollection

1。这将创建一个包含30个空值的数组

ListView

2。这将创建一个具有预定义值集的数组,您最多可以设置30个

 string[] dataArray = new string[30];

3。这样会创建一个带有字符串的数组,该字符串包含空值,您可以放置​​任何字符串值来代替 string[] dataArray = { "A", "B", "C" }; //Go up to 30 items

  

选择以上任何一种方法

我建议使用最后一种方法,然后可以将其分配给如下所示的Observable Collection。

 string[] dataArray = Enumerable.Repeat<string>(String.Empty, 30).ToArray();
  

现在的诀窍是

String.Empty

视图可能如下所示

MessagesArray = new ObservableCollection<string>(dataArray); 

答案 3 :(得分:3)

不能完全确定我有这个问题,但是据我了解,最简单的方法是:

视图模型:

只需绑定到字符串ObservableCollection,因为它已经实现了INotifyCollectionChangedINotifyPropertyChangedRelayCommand只是ICommand的实现,我假设您在做WPF MVVM以来就听说过它们。

using System.Collections.ObjectModel;

namespace WpfApp1
{
    public class MainWindowViewmodel
    {
        public ObservableCollection<string> Messages { get; set; }
        public MainWindowViewmodel()
        {
            Messages = new ObservableCollection<string>();
            Messages.Add("My message!");
            ChangeMessageCommand = new RelayCommand(ChangeMessageExcecute); 
        }
        public RelayCommand ChangeMessageCommand { get; set; }
        private void ChangeMessageExcecute() => Messages[0] = "NEW message!";
    }
}

视图:

在视图中,您可以将Textblocks绑定到Elements的{​​{1}}。当您按下按钮时,ObservableCollection会被调用并更改窗口中的消息。

Command

亲切的问候, 误导

答案 4 :(得分:2)

使用反射怎么样? 您可以索取名称为“ Msg *”的字符串类型的所有公共属性。

例如:

static class Program
{
    static void Main(string[] args)
    {
        var vm = new MessagesViewModel();

        PropertyInfo[] myProperties = vm.GetType()
            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.PropertyType == typeof(string) && p.Name.Contains("Msg"))
            .ToArray();

        foreach (var propertyInfo in myProperties)
        {
            //You can also access directly using the indexer --> myProperties[0]..
            propertyInfo.SetValue(vm, $"This is {propertyInfo.Name} property");
        }

        Console.WriteLine(vm.Msg1);
        Console.WriteLine(vm.Msg2);
    }
}

public class MessagesViewModel
{
    string _msg1;
    string _msg2;
    public string Msg1 { get => _msg1; set => _msg1 = value; }
    public string Msg2 { get => _msg2; set => _msg2 = value; }
}

如果适合这种解决方案,则可以用索引器包装它,对数组进行排序以匹配索引和Msg [num]。