문제

방금 WPF의 MVVM 패턴을 배우기 시작했습니다. 나는 벽을 쳤다 : OpenFiledialog를 보여줄 필요가있을 때 무엇을합니까??

다음은 UI를 사용하려는 예입니다.

alt text

찾아보기 버튼을 클릭하면 OpenFileDialog가 표시되어야합니다. 사용자가 OpenFileDialog에서 파일을 선택하면 텍스트 상자에 파일 경로를 표시해야합니다.

MVVM으로 어떻게 할 수 있습니까?

업데이트: MVVM으로 어떻게이를 수행하고 단위 테스트 가능하게 만들 수 있습니까? 아래 솔루션은 단위 테스트에는 효과가 없습니다.

도움이 되었습니까?

해결책

내가 일반적으로하는 것은이 기능을 수행하는 응용 프로그램 서비스를위한 인터페이스를 만드는 것입니다. 내 예에서는 MVVM 툴킷 또는 이와 유사한 것과 같은 것을 사용하고 있다고 가정 할 것입니다 (기본 뷰 모델과 릴레이 명령을 얻을 수 있음).

다음은 OpenFiledialog 및 OpenFile과 같은 기본 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);
}

이것이 도움이되기를 바랍니다.

다른 팁

그만큼 WPF 응용 프로그램 프레임 워크 (WAF) Open 및 SaveFiledialog에 대한 구현을 제공합니다.

Writer 샘플 응용 프로그램은 사용 방법과 코드를 단위 테스트 방법을 보여줍니다.

먼저 나는 당신이 WPF MVVM 툴킷. 이를 통해 프로젝트에 사용할 수있는 다양한 명령을 제공합니다. MVVM Pattern의 소개 이후로 유명한 한 가지 특별한 기능은 릴레이 명령입니다 (물론 Manny 다른 버전이 있지만 가장 일반적으로 사용되는 것을 고수합니다). ViewModel에서 새 명령을 상자로 만들 수있는 ICOMMAND 인터페이스의 구현입니다.

귀하의 질문으로 돌아가서, 여기에 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;
    }
}

ViewModelbase 그리고 릴레이 명령 둘 다 온 것입니다 MVVM 툴킷. XAML의 모습은 다음과 같습니다.

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

그리고 당신의 xaml.cs 코드 뒤에.

DataContext = new OpenFileDialogVM();
InitializeComponent();

그게 다야.

명령에 더 익숙해지면 브라우징 버튼을 비활성화하려는시기에 대한 조건을 설정할 수도 있습니다. 원하는 방향을 가리 셨기를 바랍니다.

내 관점에서 가장 좋은 선택은 프리즘 라이브러리와 상호 작용 요청입니다. 대화 상자를 열려는 동작은 XAML 내에 남아 있으며 ViewModel에서 트리거되고 ViewModel은보기에 대해 아무것도 알 필요가 없습니다.

또한보십시오

https://plainionist.github.io//mvvm-dialogs/

예를 들어 : 참조 :

https://github.com/plainionist/plainion.prism/blob/master/src/plainion.prism/interactivity/popupcommondialogaction.cs

https://github.com/plainionist/plainion.prism/blob/mas

제 생각에 최상의 솔루션은 사용자 정의 컨트롤을 만드는 것입니다.

내가 일반적으로 작성하는 사용자 정의 컨트롤은 다음에서 구성됩니다.

  • 텍스트 상자 또는 텍스트 블록
  • 템플릿으로 이미지가있는 버튼
  • 파일 경로가 래핑되는 문자열 종속성 속성

*.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}"/>
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top