Having lots of trouble with serialization in MVVM

2019-06-02 16:52发布

问题:

I am working on a text-based game in WPF and I'm exploring MVVM. Currently I have 2 models in my project, Adventurer and GameDate (I am not concerned at this point with what should or should not be a model. I'll tackle that later). I have a viewmodel MainViewModel and a view MainView. MainView has buttons bound to save/load commands... And that's sort of where I'm stuck. I would very much like to implement a form of binary serialization; I have a class ObjectSerializer that functions and the appropriate parts of which are in the Save and Load commands in MainViewModel, but I don't know how to 'obtain' access to the instances of the classes that need to be serialized (in this case the models) since I never manually instantiated any of them. Furthermore, I would like to find a way to serialize them all in one file (a typical 'save' file for a game).

Would anybody who has dealt with serialization in MVVM please be kind enough to guide me through this process? I've been stuck on this all day while making no progress and it's driving me crazy. If somebody can provide some sort of example I will be forever in your debt. Thank you in advance; an answer that would get me over this hump will not go unappreciated. I really am trying here...

ObjectSerializer.cs

    protected IFormatter iformatter;

    public ObjectSerializer()
    {
        this.iformatter = new BinaryFormatter();
    }

    public T GetSerializedObject(string filename)
    {
        if (File.Exists(filename))
        {
            Stream inStream = new FileStream(
                filename,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read);
            T obj = (T)this.iformatter.Deserialize(inStream);
            inStream.Close();
            return obj;
        }
        return default(T);
    }

    public void SaveSerializedObject(T obj, string filename)
    {
        Stream outStream = new FileStream(
            filename,
            FileMode.Create,
            FileAccess.Write,
            FileShare.None);
        this.iformatter.Serialize(outStream, obj);
        outStream.Close();
    }

回答1:

When dealing with MVVM, your Models(M) are going to be encapsulated in your ViewModel(VM) and exposed to the View(V) only through the methods and properties that you explicitly expose on your ViewModel. Your ViewModel will function mainly as an adapter between your Model(s) and you View. All of your logic interacting with your application layer, such as any of the serialization you might need, will also be housed within the ViewModel and separated from any code that is UI specific. This makes it easier to test your core application code without getting bogged down in things that you don't necessarily care about, such as if something is displayed in a TextBox or a Label. This is much more preferable over having something like object serialization happening in your xaml.cs file.

For Example:

Consider that your Adventurer class looks something like this:

public class Adventurer { 
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Rank { get; set; } //Knight, Warlock, Whatever
}

Your MainViewModel might look something like this:

(Don't worry about ViewModelBase, just assume that for the purposes of this example it houses some code which allows your MainViewModel to implement INotifyPropertyChanged, a requirement for getting it to play nice with WPF's binding subsystem)

public class MainViewModel : ViewModelBase {

    // When the ViewModel is created, populate _selectedAdventurer
    // with an empty Adventurer so that your form has something to  
    // bind to (and it can also be used as a "New" adventurer)
    private Adventurer _selectedAdventurer = new Adventurer();

    public string FirstName {
        get {
            return _selectedAdventurer.FirstName;
        }
        set {
            _selectedAdventurer.FirstName = value;
            // The following is implemented in our fictional
            // ViewModelBase, and essentially raises a notification
            // event to WPF letting it know that FirstName has changed
            OnPropertyChanged("FirstName");
        }
    }

    /*
       The remaining properties are implemented in a similar fashion, and in 
       this simple case are mainly acting as passthroughs to the view plus 
       a little bit of binding code
    */

    // These methods will house your save/load logic. I will assume
    // for simplicity that you already know how to wrap this logic in a
    // Command that can be bound to the view
    public void SaveAdventurer() {
       if(_selectedAdventurer != null) {
           SerializeToFile(_selectedAdventurer);
       }
    }

    public void LoadAdventurer() {
       _selectedAdventurer = LoadFromFile();
    }

    private void SerializeToFile(Adventurer adventurer) {
       // Use your serializer and save to file
    }

    private Adventurer LoadFromFile() {
       // Load from file and deserialize into Adventurer
    }

}

Now that you have a basic ViewModel wrapping your Model, you can easily bind UI controls to the properties on your VM once it is set as the DataContext for your view.

<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<TextBox Text="{Binding Rank}" />
<Button Value="Save" Command="{Binding SaveCommand}" />
<Button Value="Load" Command="{Binding LoadCommand}" />

Since you've set up your ViewModel to wrap your Model, and have properly bound the ViewModel properties to your View, when a user enters values into the textbox bound to FirstName, the value in _selectedAdventurer.FirstName will be directly updated with that input. Essentially, the state of your underlying model will always be in sync with the values displayed in the UI. Then when a user clicks the button labeled Save, your SaveCommand will execute and that will fire the code serialize the underlying Adventurer into a file, or database, or whatever.

This of course is a very simple example and functions mostly as a data entry form, but hopefully it will help you grasp the concept. In order to better encapsulate the Adventurer binding logic, you may choose to create a child AdventurerViewModel that will be exposed to the View, rather than putting the properties directly on MainViewModel. Perhaps you will want to add a property IEnumerable<SaveGameFile> SavegameFiles which you could bind to a DropDownList, and allow users to select which file they want to load.