MVVM:实现 ViewModel 的类不更新其 Model 实例

发布于 2024-10-25 14:40:17 字数 4554 浏览 1 评论 0原文

因此,我一直在尝试在一个简单的 WPF 应用程序中实现 MVVM 模式,该应用程序具有以下结构:

MODEL

public class Foobar
{
    public string Foo { get; set; }
    public string Bar { get; set; }

    public string DoSomethingWithFoo()
    {
        return "The quick brown fox";
    }

    public string DoSomethingWithBar()
    {
        return "jumps over the lazy dog.";
    }
}

VIEW MODEL ( BASE)

public abstract class ViewModel : INotifyPropertyChanged
{
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            Debug.Fail("Invalid property name: " + propertyName);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

视图模型 (IMPL)

public class FoobarViewModel : ViewModel
{
    private readonly Foobar foobar;

    public string Foo
    {
        get
        {
            return this.foobar.Foo;
        }
        set
        {
            this.foobar.Foo = value;
            OnPropertyChanged("Foo");
        }
    }

    public string Bar
    {
        get
        {
            return this.foobar.Bar;
        }
        set
        {
            this.foobar.Bar = value;
            OnPropertyChanged("Bar");
        }
    }

    private FoobarCommand fooCommand;
    public FoobarCommand FooCommand
    {
        get
        {
            return fooCommand;
        }
        set
        { 
            fooCommand = value;
            OnPropertyChanged("FooCommand");
        }
    }

    private FoobarCommand barCommand;
    public FoobarCommand BarCommand
    {
        get
        {
            return barCommand;
        }
        set
        { 
            barCommand = value;
            OnPropertyChanged("BarCommand");
        }
    }

    private void DoSomethingWithFoo()
    {
        if (!string.IsNullOrEmpty(this.foobar.Foo))
        {
            this.foobar.Foo = this.foobar.DoSomethingWithFoo();
            OnPropertyChanged("Foo");
        }
    }

    private void DoSomethingWithBar()
    {
        if (!string.IsNullOrEmpty(this.foobar.Bar))
        {
            this.foobar.Bar = this.foobar.DoSomethingWithBar();
            OnPropertyChanged("Bar");
        }
    }

    ///<remarks>
    /// must use the parameterless constructor to satisfy <Window.Resources>
    ///</remarks>
    public FoobarViewModel()
    {
        this.foobar = new Foobar()
        {
            Foo = "Lorem",
            Bar = "Ipsum"
        }

        this.fooCommand = new FoobarCommand(DoSomethingWithFoo);
        this.barCommand = new FoobarCommand(DoSomethingWithBar);
    };
}

命令

public class FoobarCommand : ICommand
{
    Action action;

    public FoobarCommand(Action action)
    {
        this.action = action;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        this.action.Invoke();
    }
}

视图

<Window.Resources>
    <local:FoobarViewModel x:Key="FoobarViewModel" />
</Window.Resources>

<Grid DataContext="{StaticResource FoobarViewModel}">

    <TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True}" />
    <TextBox Name="BarTextBox" Text="{Binding Bar, Mode=TwoWay, ValidatesOnDataErrors=True}" />

</Grid>

这种方法的问题是,尽管 ViewModel 与 View 绑定正常,Model 没有反映此类更改(意味着 Model通知返回< /em> 在 ViewModel 处更改其实例)

我非常感谢有关这篇文章的任何建议,提前非常感谢你们。

编辑

  1. 使用缺失的代码更新了片段(感谢 Pavlo 和 Ben)
  2. 向公共 svn 存储库提交了解决方案http://nanotaboada.svn.beanstalkapp.com/dotnet/trunk/Dotnet.Samples.Rijndael/ 适合任何有兴趣查看整个项目的人。
  3. 修改了 ModelViewModel 方法,添加了 ICommand 实现。如需完整的工作示例,请查看修订版 16。

So I've been trying to implement the MVVM pattern within a simple WPF application that has the following structure:

MODEL

public class Foobar
{
    public string Foo { get; set; }
    public string Bar { get; set; }

    public string DoSomethingWithFoo()
    {
        return "The quick brown fox";
    }

    public string DoSomethingWithBar()
    {
        return "jumps over the lazy dog.";
    }
}

VIEW MODEL (BASE)

public abstract class ViewModel : INotifyPropertyChanged
{
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            Debug.Fail("Invalid property name: " + propertyName);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

VIEW MODEL (IMPL)

public class FoobarViewModel : ViewModel
{
    private readonly Foobar foobar;

    public string Foo
    {
        get
        {
            return this.foobar.Foo;
        }
        set
        {
            this.foobar.Foo = value;
            OnPropertyChanged("Foo");
        }
    }

    public string Bar
    {
        get
        {
            return this.foobar.Bar;
        }
        set
        {
            this.foobar.Bar = value;
            OnPropertyChanged("Bar");
        }
    }

    private FoobarCommand fooCommand;
    public FoobarCommand FooCommand
    {
        get
        {
            return fooCommand;
        }
        set
        { 
            fooCommand = value;
            OnPropertyChanged("FooCommand");
        }
    }

    private FoobarCommand barCommand;
    public FoobarCommand BarCommand
    {
        get
        {
            return barCommand;
        }
        set
        { 
            barCommand = value;
            OnPropertyChanged("BarCommand");
        }
    }

    private void DoSomethingWithFoo()
    {
        if (!string.IsNullOrEmpty(this.foobar.Foo))
        {
            this.foobar.Foo = this.foobar.DoSomethingWithFoo();
            OnPropertyChanged("Foo");
        }
    }

    private void DoSomethingWithBar()
    {
        if (!string.IsNullOrEmpty(this.foobar.Bar))
        {
            this.foobar.Bar = this.foobar.DoSomethingWithBar();
            OnPropertyChanged("Bar");
        }
    }

    ///<remarks>
    /// must use the parameterless constructor to satisfy <Window.Resources>
    ///</remarks>
    public FoobarViewModel()
    {
        this.foobar = new Foobar()
        {
            Foo = "Lorem",
            Bar = "Ipsum"
        }

        this.fooCommand = new FoobarCommand(DoSomethingWithFoo);
        this.barCommand = new FoobarCommand(DoSomethingWithBar);
    };
}

COMMAND

public class FoobarCommand : ICommand
{
    Action action;

    public FoobarCommand(Action action)
    {
        this.action = action;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        this.action.Invoke();
    }
}

VIEW

<Window.Resources>
    <local:FoobarViewModel x:Key="FoobarViewModel" />
</Window.Resources>

<Grid DataContext="{StaticResource FoobarViewModel}">

    <TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True}" />
    <TextBox Name="BarTextBox" Text="{Binding Bar, Mode=TwoWay, ValidatesOnDataErrors=True}" />

</Grid>

The problem with this approach is, despite that the ViewModel is binding okay with the View, the Model is not reflecting such changes (meaning the Model is not notifying-back changes to its instance at the ViewModel)

I would really appreciate any bit of advice regarding this post, thanks much you guys in advance.

EDIT

  1. Updated snippets with the missing code (thanks Pavlo and Ben)
  2. Committed solution to a public svn repo http://nanotaboada.svn.beanstalkapp.com/dotnet/trunk/Dotnet.Samples.Rijndael/ for anyone interested in checking out the whole project.
  3. Modified Model and ViewModel methods, added ICommand implementation. For a full working sample please checkout revision 16.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

锦爱 2024-11-01 14:40:17

除了一个小但重要的细节之外,一切看起来都不错。看起来您忘记将视图的 DataContext 设置为视图模型的实例。

<Window ...
        DataContext="{StaticResource FoobarViewModel}">

如果没有它,您的绑定将会失败(在调试器下查看 Visual Studio 的输出窗口,您将看到绑定错误)。

另请注意,当 TextBox 失去焦点时,视图模型和模型中的值将会更新。要在您在绑定上键入 set UpdateSourceTrigger to PropertyChanged 时使其更新:

<TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

Everything looks OK except one small, but important detail. It looks like you forgot to set DataContext of your view to the instance of the view model.

<Window ...
        DataContext="{StaticResource FoobarViewModel}">

Without it your bindings will fail (look in the output window of Visual Studio when under debugger and you'll see binding errors).

Also note that the values will be updated in your view model and model when the TextBox looses focus. To make it update while you type set UpdateSourceTrigger to PropertyChanged on your bindings:

<TextBox Name="FooTextBox" Text="{Binding Foo, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
羁客 2024-11-01 14:40:17

在您的 FooBarViewModel 中,您没有实例化您的模型,它保留为 null,因为您将其标记为只读,所以您需要在默认构造函数中new它。

In your FooBarViewModel you are not instantiating your Model, it is left as null, since you marked it readonly, you will need to new it in a default constructor.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文