从序列化ViewModel还原时,Combox SelectedItem不适用

时间:2016-10-07 10:30:34

标签: c# json wpf mvvm deserialization

在恢复ViewModel(使用Json.Net序列化)时使用C#WPF和MVVM模式时,我遇到了一个奇怪的问题。

该软件的想法是 - 关闭窗口时 - 将当前Viewmodel状态保存在json文件中。

在下次启动时,应用程序只是为json提供了支持。

  • 如果有文件,则将其反序列化并恢复ViewModel(设置公共属性)。
  • 如果没有文件,则创建viewmodel并设置默认值。

现在我的问题是,当使用json文件恢复它时,一个包含自定义类型列表的组合框,组合框具有值但没有SelectedItem。在创建viewmodel实例并使用默认值初始化公共属性时(通过后面的代码执行此操作),一切都很好。

这是一些代表"错误"的代码: 查看

<Window x:Class="CrazyWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CrazyWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        Closing="Window_Closing"
        Loaded="Window_Loaded">
    <StackPanel
        x:Name="rootElement"
        Orientation="Vertical"
        HorizontalAlignment="Left"
        VerticalAlignment="Top"
        Margin="10">
        <StackPanel.DataContext>
            <local:DemoViewModel />
        </StackPanel.DataContext>
        <StackPanel
            Orientation="Horizontal">
            <Label
                x:Name="lblID"
                Width="30"
                Content="ID:"/>
            <TextBox
                x:Name="tbID"
                Width="50"
                Margin="30,0,0,0"
                Text="{Binding ID, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
        <StackPanel
            Orientation="Horizontal">
            <Label
                x:Name="lblName"
                Width="45"
                Content="Name:"/>
            <TextBox
                x:Name="tbName"
                Width="200"
                Margin="15,0,0,0"
                Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
        </StackPanel>
        <StackPanel
            Orientation="Horizontal">
            <Label
                x:Name="lblStai"
                Width="60"
                Content="Status:"/>
            <ComboBox
                x:Name="cbStati"
                Width="200"
                ItemsSource="{Binding StatusTypeList}"
                SelectedItem="{Binding StatusType, UpdateSourceTrigger=PropertyChanged}"
                DisplayMemberPath="Name"/>
        </StackPanel>
    </StackPanel>
</Window>

背后的代码

using System;
using System.Windows;
using System.IO;

using Newtonsoft.Json;

namespace CrazyWpf
{
    public partial class MainWindow : Window
    {
        private DemoViewModel dvm;
        public MainWindow()
        {
            InitializeComponent();

            this.dvm = (DemoViewModel)this.rootElement.DataContext;
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
            if (File.Exists(filePath))
                File.Delete(filePath);

            File.WriteAllText(filePath, JsonConvert.SerializeObject(this.dvm, Formatting.Indented));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            string filePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "settings.json");
            if (!File.Exists(filePath))
            { this.SetDefaultSettings(); return; }

            DemoViewModel d = JsonConvert.DeserializeObject<DemoViewModel>(File.ReadAllText(filePath));
            this.dvm.ID = d.ID;
            this.dvm.Name = d.Name;
            this.dvm.StatusType = d.StatusType;

        }

    }
}

BaseViewModel:

using System.ComponentModel;

namespace CrazyWpf
{
    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

视图模型

using System;
using System.Collections.Generic;
using System.Linq;

using Newtonsoft.Json;

namespace CrazyWpf
{
    class DemoViewModel : BaseViewModel
    {
        [JsonIgnore]
        private int id;
        [JsonProperty(Order = 1)]
        public int ID
        {
            get { return this.id; }
            set
            {
                if (this.id != value)
                {
                    this.id = value;
                    this.OnPropertyChanged("ID");
                }
            }
        }

        [JsonIgnore]
        private string name;
        [JsonProperty(Order = 2)]
        public string Name
        {
            get { return this.name; }
            set
            {
                if (this.name != value && value != null)
                {
                    this.name = value;
                    this.OnPropertyChanged("Name");
                }
            }
        }

        [JsonIgnore]
        private StatusTyp statusType;
        [JsonProperty(Order = 3)]
        public StatusTyp StatusType
        {
            get { return this.statusType; }
            set
            {
                if (this.statusType != value && value != null)
                {
                    this.statusType = value;
                    this.OnPropertyChanged("StatusType");
                }
            }
        }

        [JsonIgnore]
        private List<StatusTyp> statusTypeList;
        [JsonProperty(Order = 4)]
        public List<StatusTyp> StatusTypeList
        {
            get { return this.statusTypeList; }
            set
            {
                if (this.statusTypeList != value && value != null)
                {
                    this.statusTypeList = value;
                    this.OnPropertyChanged("StatusTypeList");
                }
            }
        }

        public DemoViewModel()
        {
            this.StatusTypeList = new Func<List<StatusTyp>>(() =>
            {
                var list = Enum.GetValues(typeof(Status))
                    .Cast<Status>()
                    .ToDictionary(k => (int)k, v => v.ToString())
                    .Select(e => new StatusTyp()
                    {
                        Value = e.Key,
                        Name = e.Value,
                        Status =
                            Enum.GetValues(typeof(Status))
                                .Cast<Status>().
                                Where(x =>
                                {
                                    return (int)x == e.Key;
                                }).FirstOrDefault()
                    })
                    .ToList();
                return list;
            })();
        }
    }

    public class StatusTyp
    {
        public int Value { get; set; }
        public string Name { get; set; }
        public Status Status { get; set; }
    }

    public enum Status
    {
        NotDetermined = 0,
        Determined = 1,
        Undeterminded = 2,
        Unknown = 3
    }
}

1 个答案:

答案 0 :(得分:1)

如果您有ItemsSource和SelectedItem,则SelectedItem中的实例必须位于绑定到ItemsSource的集合中。如果不是,那么您的绑定将无法按预期工作。

控件使用引用相等来确定ItemsSource中的哪个项是SelectedItem中的项并更新UI。这通常不是问题,因为控件会为您填充SelectedItem,但如果您要从ViewModel端进行更新,则必须确保正确管理您的引用。

序列化/反序列化视图模型时,这可能是一个问题。大多数常见的序列化程序不跟踪引用,因此无法在反序列化时恢复这些引用。可以在原始对象图中的多个位置引用相同的对象,但是在反序列化之后,现在在整个再水合图中具有多个原始遍布的实例。这不符合您的要求。

您需要做的是,在反序列化后,在集合中找到匹配的实例,并将其替换为SelectedItem中的实例。或者,使用serializer that tracks instances.。 XAML序列化程序已经这样做了,对于.net对象图来说,它是一个非常好的xml序列化器。