2009-10-24 29 views
85

Tôi mới bắt đầu học mẫu MVVM cho WPF. Tôi nhấn một bức tường: bạn sẽ làm gì khi cần hiển thị OpenFileDialog?WPF OpenFileDialog với mẫu MVVM?

Dưới đây là một giao diện người dùng ví dụ tôi đang cố gắng để sử dụng nó trên:

alt text

Khi nút browse được nhấp, một OpenFileDialog sẽ được hiển thị. Khi người dùng chọn một tệp từ OpenFileDialog, đường dẫn tệp sẽ được hiển thị trong hộp văn bản.

Tôi làm cách nào để thực hiện việc này với MVVM?

Cập nhật: Làm cách nào tôi có thể thực hiện việc này với MVVM và làm cho đơn vị có thể kiểm tra? Giải pháp bên dưới không hoạt động đối với thử nghiệm đơn vị.

+0

Đây là một bản sao của: http://stackoverflow.com/questions/1043918/ open-file-dialog-mvvm –

+0

Chắc chắn là. :-) Tôi đã thực hiện một số tìm kiếm trên SO trước khi đăng câu hỏi này, nó không xuất hiện. Oh well. –

+0

Tôi đã bỏ phiếu để đóng câu hỏi này vì đây là một bản sao chính xác. –

Trả lời

84

Điều tôi thường làm là tạo giao diện cho dịch vụ ứng dụng thực hiện chức năng này. Trong ví dụ của tôi, tôi sẽ giả sử bạn đang sử dụng một cái gì đó như MVVM Toolkit hoặc điều tương tự (vì vậy tôi có thể có được một ViewModel cơ bản và một RelayCommand).

Dưới đây là ví dụ về giao diện cực kỳ đơn giản để thực hiện các thao tác IO cơ bản như OpenFileDialog và OpenFile. Tôi đang hiển thị cả hai ở đây vì vậy bạn không nghĩ rằng tôi đề nghị bạn tạo một giao diện với một phương pháp để giải quyết vấn đề này.

public interface IOService 
{ 
    string OpenFileDialog(string defaultPath); 

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

Trong ứng dụng của bạn, bạn sẽ cung cấp triển khai mặc định dịch vụ này. Đây là cách bạn sẽ tiêu thụ nó.

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; 
      } 
    } 
} 

Điều đó khá đơn giản. Bây giờ cho phần cuối cùng: testability. Điều này nên rõ ràng, nhưng tôi sẽ chỉ cho bạn cách thực hiện một thử nghiệm đơn giản cho việc này. Tôi sử dụng Moq cho stubbing, nhưng bạn có thể sử dụng bất cứ điều gì bạn muốn tất nhiên.

[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); 
} 

Điều này có thể hữu ích cho bạn.

Có thư viện trên CodePlex được gọi là "SystemWrapper" (http://systemwrapper.codeplex.com) có thể giúp bạn không phải làm một số của loại điều này. Dường như FileDialog chưa được hỗ trợ, vì vậy bạn chắc chắn sẽ phải viết một giao diện cho cái đó.

Hy vọng điều này sẽ hữu ích.

Sửa:

tôi dường như nhớ đến bạn ưu TypeMock Isolator cho khung giả mạo lòi của bạn. Đây là thử nghiệm tương tự bằng cách sử dụng 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); 
} 

Hy vọng điều này cũng hữu ích.

+0

Điều này có ý nghĩa với tôi: có một số dịch vụ thực hiện các hộp thoại như thế này và sử dụng dịch vụ đó thông qua giao diện trong ViewModel. Tuyệt vời cảm ơn bạn. (p.s. Tôi sẽ được thử nghiệm với RhinoMocks, FYI, nhưng tôi có thể tìm ra một phần không có vấn đề gì.) –

+0

Shucks. Ở đây tôi nghĩ rằng tôi đã được ưa thích. Mừng vì tôi có thể giúp. –

+0

Lỗi đánh máy nhỏ trong đoạn thứ hai FYI :). Cảm ơn câu trả lời! – Jeff

2

Trước tiên, tôi khuyên bạn nên bắt đầu bằng một số WPF MVVM toolkit. Điều này cung cấp cho bạn một lựa chọn tốt đẹp của lệnh để sử dụng cho các dự án của bạn. Một tính năng đặc biệt đã được làm nổi tiếng kể từ khi giới thiệu mẫu MVVM là RelayCommand (có nhiều phiên bản khác của manny, nhưng tôi chỉ gắn bó với các phiên bản phổ biến nhất). Nó thực hiện giao diện ICommand cho phép bạn tạo một lệnh mới trong ViewModel của bạn.

Quay lại câu hỏi của bạn, dưới đây là ví dụ về diện mạo ViewModel của bạn.

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 đều từ MVVM Toolkit. Đây là những gì XAML có thể trông như thế nào.

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

và mã XAML.CS của bạn phía sau.

DataContext = new OpenFileDialogVM(); 
InitializeComponent(); 

Thats it.

Khi bạn làm quen với các lệnh, bạn cũng có thể đặt các điều kiện về thời điểm bạn muốn nút Duyệt bị vô hiệu hóa, v.v. Tôi hy vọng điều đó sẽ hướng bạn theo hướng bạn muốn.

+6

Tôi nên nói lại: làm cách nào để tôi có thể kiểm tra đơn vị này? Giải pháp của bạn sẽ bật lên một hộp thoại khi chạy các bài kiểm tra đơn vị. –

+0

Nếu bạn nhà ViewModel của bạn trong DLL riêng của nó, nó không nên có một tham chiếu đến PresentationFramework.dll. –

+1

Tại sao bạn sử dụng 'RegisterCommands();' thay vì viết trực tiếp 'OpenCommand = new RelayCommand (ExecuteOpenFileDialog);'? – aloisdg

4

WPF Application Framework (WAF) cung cấp triển khai cho Open and SaveFileDialog.

Ứng dụng mẫu Writer hiển thị cách sử dụng chúng và cách mã có thể được kiểm tra đơn vị.

1

Theo tôi, giải pháp tốt nhất là tạo điều khiển tùy chỉnh.

Việc kiểm soát tùy chỉnh Tôi thường tạo bao gồm:

  • Textbox hoặc TextBlock
  • Nút với một hình ảnh như mẫu
  • tài sản phụ thuộc
  • Chuỗi nơi đường dẫn tập tin sẽ được bao bọc để

Vì vậy, tệp * .xaml sẽ như thế này

<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> 

Và * .cs file:

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; 
     } 
    } 

Cuối cùng bạn có thể gắn nó vào mô hình xem của bạn:

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

Từ quan điểm của tôi lựa chọn tốt nhất là thư viện lăng kính và InteractionRequests. Hành động mở hộp thoại vẫn nằm trong xaml và được kích hoạt từ ViewModel trong khi ViewModel không cần biết gì về khung nhìn.

cũng

https://plainionist.github.io///Mvvm-Dialogs/

Xem Như ví dụ xem:

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/PopupCommonDialogAction.cs

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs