XAML 是否使用MVVM模式的WPF OpenFileDialog?[重复]

lpwwtiir  于 2022-12-25  发布在  其他
关注(0)|答案(5)|浏览(219)
    • 此问题在此处已有答案**:

Open File Dialog MVVM(7个答案)
两年前关闭了。
我刚开始学习WPF的MVVM模式,遇到了一个难题:* * 当您需要显示OpenFileDialog时,应该怎么做**?
下面是我尝试使用它的示例UI:

点击浏览按钮时,应显示OpenFileDialog,用户从OpenFileDialog中选择文件时,文本框中应显示文件路径。
如何使用MVVM实现这一点?

    • 更新**:如何使用MVVM进行此操作并使其可进行单元测试?下面的解决方案不适用于单元测试。
i86rm4rw

i86rm4rw1#

我通常做的是为执行此功能的应用程序服务创建一个接口,在我的示例中,我将假设您正在使用MVVM Toolkit或类似的工具(这样我就可以获得一个基本ViewModel和一个RelayCommand)。
下面是一个非常简单的接口示例,用于执行OpenFileDialogOpenFile等基本IO操作。我在这里同时显示它们,因此您不会认为我建议您创建一个接口和一个方法来解决此问题。

public interface IOService
{
     string OpenFileDialog(string defaultPath);

     //Other similar untestable IO operations
     Stream OpenFile(string path);
}

在您的应用程序中,您将提供此服务的默认实现,下面是您使用它的方式。

public MyViewModel : ViewModel
{
     private string _selectedPath;
     public string SelectedPath
     {
          get { return _selectedPath; }
          set { _selectedPath = value; OnPropertyChanged("SelectedPath"); }
     }

     private RelayCommand _openCommand;
     public RelayCommand OpenCommand
     {
          //You know the drill.
          ...
     }
     
     private IOService _ioService;
     public MyViewModel(IOService ioService)
     {
          _ioService = ioService;
          OpenCommand = new RelayCommand(OpenFile);
     }

     private void OpenFile()
     {
          SelectedPath = _ioService.OpenFileDialog(@"c:\Where\My\File\Usually\Is.txt");
          if(SelectedPath == null)
          {
               SelectedPath = string.Empty;
          }
     }
}

这很简单。现在是最后一部分:可测试性。这一点应该是显而易见的,但我将向您展示如何对此进行简单的测试。我使用Moq进行存根,但您当然可以使用任何您喜欢的方法。

[Test]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
     Mock<IOService> ioServiceStub = new Mock<IOService>();
     
     //We use null to indicate invalid path in our implementation
     ioServiceStub.Setup(ioServ => ioServ.OpenFileDialog(It.IsAny<string>()))
                  .Returns(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub.Object);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}

这可能对你有用。
CodePlex上有一个名为"SystemWrapper"(http://systemwrapper.codeplex.com)的库,它可以让你不必做很多类似的事情。看起来FileDialog还不受支持,所以你必须为它编写一个接口。

    • 编辑**:

我好像记得你喜欢用TypeMock Isolator作为你的伪装框架,下面是使用Isolator的相同测试:

[Test]
[Isolated]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
    IOService ioServiceStub = Isolate.Fake.Instance<IOService>();

    //Setup stub arrangements
    Isolate.WhenCalled(() => ioServiceStub.OpenFileDialog("blah"))
           .WasCalledWithAnyArguments()
           .WillReturn(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}
tsm1rwdh

tsm1rwdh2#

WPF Application Framework (WAF)提供了打开和保存文件对话框的实现。
Writer示例应用程序显示了如何使用它们以及如何对代码进行单元测试。

brccelvz

brccelvz3#

首先,我建议您从WPF MVVM toolkit开始,这为您的项目提供了一个很好的命令选择,自从MVVM模式引入以来,一个特别著名的特性就是RelayCommand(当然还有曼尼的其他版本,但我只是坚持使用最常用的)。它是ICommand接口的实现,允许您在ViewModel中创建一个新命令。
回到您的问题,下面是一个ViewModel的示例。

public class OpenFileDialogVM : ViewModelBase
{
    public static RelayCommand OpenCommand { get; set; }
    private string _selectedPath;
    public string SelectedPath
    {
        get { return _selectedPath; }
        set
        {
            _selectedPath = value;
            RaisePropertyChanged("SelectedPath");
        }
    }

    private string _defaultPath;

    public OpenFileDialogVM()
    {
        RegisterCommands();
    }

    public OpenFileDialogVM(string defaultPath)
    {
        _defaultPath = defaultPath;
        RegisterCommands();
    }

    private void RegisterCommands()
    {
        OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
    }

    private void ExecuteOpenFileDialog()
    {
        var dialog = new OpenFileDialog { InitialDirectory = _defaultPath };
        dialog.ShowDialog();

        SelectedPath = dialog.FileName;
    }
}

ViewModelBaseRelayCommand都来自MVVM Toolkit。XAML可能如下所示。

<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>

以及XAML.CS代码。

DataContext = new OpenFileDialogVM();
InitializeComponent();

就是这样。
随着您对这些命令的熟悉,您还可以设置一些条件,例如何时禁用Browse按钮等。我希望这为您指明了您想要的方向。

w80xi6nr

w80xi6nr5#

在我看来,最好的解决方案是创建一个自定义控件。
我通常创建的自定义控件由以下内容组成:

  • 文本框或文本块
  • 以图像为模板的按钮
  • 文件路径将被 Package 到的字符串依赖项属性

因此,*.xaml文件将如下所示

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    <Button Grid.Column="1" Click="Button_Click">
        <Button.Template>
            <ControlTemplate>
                <Image Grid.Column="1" Source="../Images/carpeta.png"/>
            </ControlTemplate>                
        </Button.Template>
    </Button>        
</Grid>

和 *.cs文件:

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
        typeof(string),
        typeof(customFilePicker),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal));

public string Text
{
    get
    {
        return this.GetValue(TextProperty) as String;
    }
    set
    {
        this.SetValue(TextProperty, value);
    }
}

public FilePicker()
{
    InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog openFileDialog = new OpenFileDialog();

    if(openFileDialog.ShowDialog() == true)
    {
        this.Text = openFileDialog.FileName;
    }
}

最后,您可以将其绑定到视图模型:

<controls:customFilePicker Text="{Binding Text}"/>

相关问题