【问题标题】:Importing Image MVVM WPF导入图像 MVVM WPF
【发布时间】:2015-02-23 00:28:54
【问题描述】:

我是 WPF 新手,在尝试学习它时遇到了 MVVM 框架。现在我正在尝试使用我制作的一个简单的应用程序来实现它,该应用程序可以导入并显示图像。

XAML:

<Window x:Class="mvvmSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Width="1024" Height="768">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <GroupBox Grid.Row="0" Header="Imported Picture">
        <Image x:Name="_image" Stretch="Fill"/>
    </GroupBox>
    <Button Height="50" Grid.Row="1" Content="Import Picture"    Click="Button_Click"/>
    </Grid>
</Window>

代码背后:

using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Media.Imaging;

namespace mvvmSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }


    private void Button_Click(object sender, RoutedEventArgs e)
    {
        OpenFileDialog open = new OpenFileDialog();
        open.DefaultExt = (".png");
        open.Filter = "Pictures (*.jpg;*.gif;*.png)|*.jpg;*.gif;*.png";

        if (open.ShowDialog() == true)
            _image.Source = new BitmapImage(new Uri(open.FileName));
      }
   }
}

我看了很多关于 mvvm 初学者的教程,阅读了很多关于它的文章,我掌握了它背后的概念。对于我的应用程序,我假设视图与我所拥有的视图相同,但不使用事件,而是对源和按钮使用命令绑定。对于模型,我假设我应该有一个图像属性,但我不确定它是否应该获取并设置文件路径或图像本身。然后,视图模型将包含用于图像检索 (OpenFileDialog) 和按钮命令的函数。我的假设是否正确,或者是否有更好的方法将此应用程序转换为 mvvm。一个示例编码会很棒,所以我可以分析它。

提前致谢,

【问题讨论】:

  • 去试试 Caliburn.Micro; ViewModels 作为 View 的资源是一种可怕的模式。我建议您使用 NInject 或 Autofac 或类似方法实现依赖注入。相信我,如果你选择像 Caliburn.Micro 这样的体面的框架,你会更喜欢 MVVM。

标签: c# wpf xaml mvvm


【解决方案1】:

在 ViewModel 中,您应该定义单击按钮时要执行的逻辑。为此,您需要使用命令。我的建议是使用RelayCommand,这是一个通用命令:

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    //[DebuggerStepThrough]
    /// <summary>
    /// Defines if the current command can be executed or not
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members
}

假设您使用上面定义的RelayCommand,您需要为其提供一个或两个委托,一个返回一个布尔值,用于确定命令是否处于要运行的有效状态,另一个返回什么都没有,实际上运行命令。如果您不提供CanExecute 委托,那么该命令将认为它始终处于有效状态。

在您的 ViewModel 中,您还需要一个属性来保存图像路径。此属性将与您在视图中拥有的Image 的Source 属性绑定。因此,您的 ViewModel 类将是这样的:

public class MainViewModel: INotifyPropertyChanged
{
    private string imagePath;

    public string ImagePath
    {
        get { return imagePath; }
        set
        {
            imagePath = value;
            SetPropertyChanged("ImagePath");
        }
    }

    ICommand _loadImageCommand;
    public ICommand LoadImageCommand
    {
        get
        {
            if (_loadImageCommand == null)
            {
                _loadImageCommand = new RelayCommand(param => LoadImage());
            }
            return _loadImageCommand;
        }
    }

    private void LoadImage()
    {
        OpenFileDialog open = new OpenFileDialog();
        open.DefaultExt = (".png");
        open.Filter = "Pictures (*.jpg;*.gif;*.png)|*.jpg;*.gif;*.png";

        if (open.ShowDialog() == true)
            ImagePath = open.FileName;
    }

    #region Property Changed Event Handler
    protected void SetPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion Property Changed Event Handler
}

使用此 ViewModel,您无需在 View 的代码开头执行任何操作。您只需在窗口的资源中实例化您的 ViewModel 并设置根网格的 DataContext 属性。之后,您可以使用适当的控件绑定属性和命令:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpfApplication1="clr-namespace:WpfApplication1"
    Title="MainWindow" Width="1024" Height="768">
<Window.Resources>
    <wpfApplication1:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <GroupBox Grid.Row="0" Header="Imported Picture">
        <Image x:Name="_image" Stretch="Fill" Source="{Binding ImagePath}"/>
    </GroupBox>
    <Button Height="50" Grid.Row="1" Content="Import Picture" Command="{Binding LoadImageCommand}"  />
</Grid>

【讨论】:

  • 感谢您的回复。所以我猜如果我实现其他按钮(如缩放和保存)可以使用通用类 relaycommand,但是对于 ICommand 是否也可以使其通用?另外,我认为我需要在其中添加 ViewModel 命名空间,以便我可以调用(如果使用正确的措辞)它作为窗口资源,因为我尝试使用它,而 wpfApplication1 只显示 APP 而没有 MainViewMODEl。
  • 是的,当您需要定义您提到的事件之一时,您可以使用 RelayCommand。我在答案中显示的命名空间是我在应用程序中定义 ViewModel 的命名空间,因此,您需要自己更改它。
【解决方案2】:

致 Octavioccl

我确实在我的编码中使用了自己的命名空间:

<Window x:Class="mvvmSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mvvmSample="clr-namespace:mvvmSample"
    xmlns:vm="clr-namespace:mvvmSample.ViewModel"
    Title="MainWindow" Width="1024" Height="768">
<Window.Resources>
    <vm:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <GroupBox Grid.Row="0" Header="Imported Picture">
        <Image x:Name="_image" Stretch="Fill" Source="{Binding ImagePath}" />
    </GroupBox>
    <Button Height="50" Grid.Row="1" Content="Import Picture" Command="{Binding LoadedImageCommand}"/>
</Grid>

如你所见,我两者都有:

xmlns:mvvmSample="clr-namespace:mvvmSample"
xmlns:vm="clr-namespace:mvvmSample.ViewModel"

我添加了后者,因为没有它我无法访问 Window.Resources 中的 MainViewModel,并且 mvvmSample 只能选择 App。该程序运行但它不做任何事情它只是显示用户界面,如果我点击按钮没有任何反应。我在 RelayCommand 和 MainWindow 代码后面的几个地方放置了断点,以便能够观察问题,但它只是初始化组件并显示 UI。

更新:

我能够解决这个完全是我的错的问题。我在绑定中写了错误的函数名称而不是 LoadImage 我有 LoadedImage 这就是为什么我的按钮单击没有激活“DUH”。感谢您的所有帮助。

【讨论】:

    猜你喜欢
    • 2013-11-08
    • 2016-01-29
    • 1970-01-01
    • 2019-01-02
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    相关资源
    最近更新 更多