programing

파일 대화상자 열기 MVVM

mbctv 2023. 4. 21. 21:17
반응형

파일 대화상자 열기 MVVM

알겠습니다. 전문 MVVM 개발자가 WPF에서 열린 파일 대화 상자를 어떻게 처리하는지 알고 싶습니다.

ViewModel(DelegateCommand를 통해 '찾아보기'를 참조)에서는 이 작업을 수행하지 않습니다.

void Browse(object param)
{
    //Add code here
    OpenFileDialog d = new OpenFileDialog();

    if (d.ShowDialog() == true)
    {
        //Do stuff
    }
}

그것은 MVVM의 방법론에 어긋난다고 생각하기 때문입니다.

내가 어떻게 해야 하나요?

★★★★★★★★★★★★★★★★★★:
해결책은 보기 구성 요소의 일부인 클래스의 사용자 상호 작용을 표시하는 것입니다.
즉, 이러한 클래스는 뷰 모델에서 알 수 없는 클래스여야 하므로 뷰 모델에서 호출할 수 없습니다.
솔루션이 MVVM에 준거하고 있는지 여부를 평가할 때 코드 이면은 관련이 없기 때문에 솔루션에는 코드 이면의 구현이 수반될 수 있습니다.

이 답변은 원래 질문에 대답하는 것 외에 뷰 모델에서 대화상자와 같은 UI 컴포넌트를 제어하는 것이 MVVM 설계 패턴을 위반하는 이유와 대화상자 서비스 같은 회피책으로 문제를 해결할 수 없는 이유에 대한 대체적인 견해를 제시하려고 합니다.

1개의 MVVM 및 대화상자

1.1 일반적인 제안의 비평

거의 모든 답변은 MVVM이 하나의 패턴이며 클래스 수준의 의존성을 대상으로 하며 빈 코드 배후에 파일이 필요하다는 잘못된 인식을 따르고 있습니다.그러나 아키텍처 패턴은 애플리케이션/컴포넌트 수준에서 비즈니스 도메인이 UI에서 분리되도록 하는 다른 문제를 해결하려고 합니다.
대부분의 사용자(여기 SO)는 뷰 모델이 대화 상자를 처리하지 않아야 한다는 데 동의하고 UI 관련 로직을 뷰 모델에 의해 제어되는 도우미 클래스(도우미 또는 서비스라고 해도 상관 없음)로 이동할 것을 제안합니다.
이 기능(특히 서비스 버전)은 의존성 숨김이라고도 합니다.많은 패턴들이 이런 일을 합니다.이러한 패턴은 안티 패턴으로 간주됩니다.Service Locator는 가장 유명한 안티 패턴 숨기기 의존관계입니다.

그렇기 때문에 뷰 모델 클래스에서 다른 클래스로 UI 로직을 추출하는 패턴도 안티 패턴이라고 부릅니다.뷰 모델(또는 모델) 클래스에서 UI 관련 책임을 제거하고 뷰 관련 클래스로 다시 이동하기 위해 응용 프로그램 구조 또는 클래스 설계를 변경하는 방법으로는 해결할 수 없습니다.
은 뷰 컴포넌트의 로 있습니다.

따라서 (인터페이스 뒤에 숨겨져 있는지 여부에 관계없이) 다이얼로그 서비스를 수반하는 솔루션(승인된 솔루션과 같은 솔루션)을 실장하는 것은 권장하지 않습니다.MVVM 설계 패턴을 준수하는 코드를 작성해야 하는 경우 보기 모델 내에서 대화 상자 보기나 메시지를 처리하지 마십시오.

레벨하기 위한 예: " " " " " " " " " " " " " " " "IFileDialogService인터페이스는 의존성 반전 원리(SOLID에서는 D)라고 불리며 MVVM과는 관계가 없습니다.MVVM과 관련성이 없는 경우에는 MVVM 관련 문제를 해결할 수 없습니다.상온이 4층 건물인지 초고층 건물인지 아무런 관련이 없을 때, 상온을 바꾸는 것은 어떤 건물도 초고층 건물로 바꿀 수 없다.MVVMDependency Inversion의 동의어가 아닙니다.

MVVM은 아키텍처 패턴인 반면 Dependency Inversion은 애플리케이션(일명 소프트웨어 아키텍처) 구조화와 무관한 OO 언어 원리입니다.애플리케이션을 구성하는 것은 인터페이스(또는 추상형)가 아니라 컴포넌트나 모듈 등의 추상 객체 또는 엔티티입니다.[모델] - [보기] - [보기] - [모델]인터페이스는 컴포넌트 또는 모듈을 "물리적으로" 분리하는 데만 도움이 됩니다.컴포넌트 연결은 삭제되지 않습니다.

2 1.2를 처리하는 Window상하고고 고고고?

이 된다, 라는 것을 .Microsoft.Win32.OpenFileDialog는 Windows 네이티브컨트롤입니다.MVVM 환경에 원활하게 통합하는 데 필요한 API가 없습니다.그 본질 때문에, WPF와 같은 높은 수준의 프레임워크에 통합할 수 있는 방법에는 몇 가지 제한이 있다.일반적으로 대화 상자 또는 네이티브 창 호스트는 WPF와 같은 모든 고급 프레임워크의 알려진 "약점"이다.

상자는 으로 「」에 .Window 추상적인 '추상관적인 것.CommonDialog ㅇㅇㅇㅇㅇ.Window입니다.ContentControl따라서 스타일과 템플릿이 내용을 대상으로 할 수 있습니다.
가지 큰 은 'A'라는 Window는 항상 루트 요소여야 합니다. 트리에서 예를 들어 트리거를 사용하여 표시/기동하거나 호스트 할 수 없습니다.DataTemplate.
CommonDialog수 . 비주얼 되어 있지 때문입니다UIElement.

therefore그 、Window ★★★★★★★★★★★★★★★★★」CommonDialog.code-behind는 이러한 적절하게 하는 것에 큰 을 일으키는 합니다.
또한 많은 개발자, 특히 MVVM을 처음 접하는 초보자들은 코드 배후에 MVVM이 위반된다는 인식을 가지고 있습니다.
비이성적인 이유로 뷰 모델 구성요소에서 대화 상자 뷰를 처리하는 것이 위반 사항이 적습니다.

due 인 due 인 due 。Window제어처럼 은 확장되어 ).ContentControl하지만, 그 아래에서는 OS의 낮은 레벨에 접속합니다.이를 실현하기 위해 필요한 관리되지 않는 코드가 많이 있습니다.MFC와 같은 하위 레벨의 C++ 프레임워크에서 온 개발자들은 어떤 일이 벌어지고 있는지 정확히 알고 있습니다.

Window ★★★★★★★★★★★★★★★★★」CommonDialog클래스는 둘 다 진정한 하이브리드입니다.WPF 프레임워크의 일부이지만 네이티브 OS 윈도처럼 동작하거나 실제로 네이티브 OS가 되기 위해서는 저레벨 OS 인프라스트럭처의 일부이기도 합니다.
WPFWindow및을 참조해 주세요.CommonDialog기본적으로는 복잡한 저레벨 OS API를 둘러싼 래퍼입니다.그렇기 때문에 이 컨트롤은 일반적이고 순수한 프레임워크 컨트롤과 비교했을 때 (개발자의 관점에서) 이상한 느낌이 들 수 있습니다.
★★★WindowContentControl을 사용하다 WPF는 의 프레임워크이기 에 WPF의 모든 의 세부 는 설계에 .
.Window ★★★★★★★★★★★★★★★★★」CommonDialogC#만 사용 - 이 코드 배후에 설계 패턴이 전혀 위반되지 않습니다.

인 OS 하고 네이티브 등)을 취득할 수 는, 으로, 예를 를 확장해 할 수 .Control ★★★★★★★★★★★★★★★★★」Popup관련 속성을 공개합니다.DependencyProperty그런 다음 데이터 바인딩 및 XAML 트리거를 설정하여 평소처럼 가시성을 제어할 수 있습니다.

1.3 MVVM이 필요한 이유

정교한 설계 패턴이나 애플리케이션 구조가 없다면 개발자는 데이터베이스 데이터를 테이블 제어에 직접 로드하고 UI 논리와 비즈니스 논리를 혼합할 수 있습니다.이러한 경우 다른 데이터베이스로 변경하면 UI가 손상됩니다.게다가 UI를 변경하려면 데이터베이스를 다루는 로직을 변경해야 합니다.또한 로직을 변경할 때는 관련된 유닛 테스트도 변경해야 합니다.

실제 어플리케이션은 비즈니스 로직이지 화려한 GUI가 아닙니다.
강제로 UI를 포함하지 않고 비즈니스 로직에 대한 단위 테스트를 작성하려고 합니다.
비즈니스 로직 및 단위 테스트를 수정하지 않고 UI를 수정하려고 합니다.

MVVM은 이 문제를 해결하고 UI를 비즈니스 로직(예: 보기에서 데이터)에서 분리할 수 있는 패턴입니다.이는 관련된 설계 패턴 MVC MVP보다 더 효율적으로 수행됩니다.

UI를 애플리케이션의 하위 레벨로 옮기고 싶지 않습니다.데이터 프레젠테이션, 특히 렌더링(데이터 뷰)에서 데이터를 분리하고 싶습니다.예를 들어 데이터를 표시하기 위해 사용되는 라이브러리나 컨트롤은 신경 쓰지 않고 데이터베이스 액세스를 처리할 수 있습니다.그래서 MVVM을 선택했습니다.따라서 뷰 이외의 컴포넌트에서는 UI 로직을 구현할 수 없습니다.

4 왜 1.4 UI라는 이름의 하는가?ViewModel아직 MVVM을 위반하고 있습니다.

MVVM을 적용하면 애플리케이션을 모델, 뷰 및 뷰 모델의 세 가지 구성요소로 효과적으로 구성할 수 있습니다.이 파티셔닝 또는 구조는 클래스에 관한 이 아니라는 것을 이해하는 것이 매우 중요합니다.애플리케이션 컴포넌트에 관한 것입니다.
를 붙일 수 .ViewModel, 뷰 컴포넌트에는 않거나 않는 가 많이 ViewModel컴포넌트입니다. - View Model은 추상 컴포넌트입니다.

§:
할 때, '데이터 소스 컬렉션'이라는 합니다.MainViewModel이 기능을 새로운 클래스로 이동합니다.ItemCreator 후 이 클래스는 '''입니다 "ItemCreator는 논리적으로 뷰 모델 구성요소의 일부입니다.
에서는, 이 은 「」, 「」 되었습니다.MainViewModel)MainViewModel이제 코드를 호출하기 위해 새 클래스에 대한 강력한 참조가 있습니다.)애플리케이션 레벨(아키텍처 레벨)에서는, 기능은 아직 같은 컴포넌트에 있습니다.

는 자주 서비스에 할 수 뷰을 전용 할 수 있습니다.DialogService에서는 로직이 뷰 모델컴포넌트 외부로 이동하지 않습니다.뷰 모델은 추출된 기능에 의존합니다.
보기 모델은 대화 상자가 표시되는 시기를 제어하고 대화 상자 유형 자체(예: 파일 열기, 폴더 선택, 색상 선택 등)를 제어하기 위해 명시적으로 "서비스"를 호출하여 UI 로직에 계속 참여합니다.
이를 위해서는 UI의 비즈니스 세부 정보를 알아야 합니다.정의당 뷰 모델 구성 요소에 속하지 않는 지식입니다.물론 이러한 노하우에 의해 뷰 모델 컴포넌트에서 뷰 컴포넌트로의 결합/의존성이 도입됩니다.

을 붙이기 DialogService예예 、 를를 、 ,를 、DialogViewModel.

DialogService따라서 는 실제 문제를 감추는 안티패턴입니다.이는 UI에 의존하여 UI 로직을 실행하는 뷰 모델클래스를 실장하고 있는 것입니다.

1.5 코드 배후에 쓰는 것은 MVVM 설계 패턴에 위배됩니까?

MVVM은 설계 패턴이며 설계 패턴은 정의 라이브러리에 따라 독립적이며 프레임워크에 의존하지 않으며 언어 또는 컴파일러에 의존하지 않습니다.따라서 MVVM에 대해 언급할 때 코드 배후에 대한 주제는 아닙니다.

코드 배후에 있는 파일은 UI 코드를 작성하기 위한 유효한 컨텍스트입니다.C# 코드가 포함된 다른 파일입니다.code-behind는 ".xaml.cs 확장자를 가진 파일"을 의미합니다.이벤트 핸들러를 위한 유일한 장소이기도 합니다.그리고 당신은 사건에서 멀어지고 싶지 않을 것이다.

코드 이면에 코드가 없습니다라는 주문은 왜 존재합니까?
WinForms와 같은 프레임워크에서 온 숙련되고 경험이 풍부한 개발자와 같이 WPF, UWP 또는 Xamarin을 처음 접하는 사람들에게는 XAML을 사용하는 것이 UI 코드를 작성하는 데 선호되어야 한다는 점을 강조해야 합니다.Style ★★★★★★★★★★★★★★★★★」DataTemplateC#(예: 코드 뒤에 있는 파일)을 사용하는 것은 너무 복잡하며 => 이해하기 어려운 => 유지보수가 어려운 코드를 생성합니다.
XAML을 사용합니다.시각적으로 상세한 마크업 스타일은 UI의 구조를 완벽하게 표현합니다.예를 들어 C#이 할 수 있는 것보다 훨씬 더 잘 할 수 있습니다.XAML과 같은 마크업 언어는 일부에 비해 열등하다고 느낄 수도 있고 배울 가치가 없다고 느낄 수도 있지만, GUI를 구현할 때는 이것이 단연 첫 번째 선택입니다.XAMLX를 한 한 .AML을 사용하여 가능한 한 많은 GUI 코드를 작성하도록 노력해야 합니다.

그러나 이러한 고려 사항은 MVVM 설계 패턴과 전혀 관련이 없습니다.

에 있는 컴파일러의 개념에 됩니다.partial(C#은)는, 「C#」의 「C#는 「C」입니다.그렇기 때문에 코드 배후에 있는 것은 디자인 패턴과는 관계가 없습니다.XAML C#입니다.


2 솔루션

OP의 올바른 결론처럼:

"내 ViewModel(DelegateCommand를 통해 'Browse'를 참조)에서 이 작업을 [파일 선택 대화 상자 열기]하고 싶지 않습니다.그것은 MVVM의 방법론에 어긋난다고 생각하기 때문입니다.

2.1 몇 가지 기본적인 고려사항

  • 대화 상자는 UI 컨트롤: 보기입니다.
  • 대화 상자 컨트롤 또는 일반적으로 컨트롤(예: 표시/숨김)을 처리하는 것은 UI 로직입니다.
  • MVVM 요구 사항: 뷰 모델은 UI 또는 사용자의 존재를 인식하지 않습니다.따라서 뷰 모델이 적극적으로 대기하거나 사용자 입력을 호출해야 하는 제어 흐름은 실제로 일부 설계를 다시 수행해야 합니다. 이는 중대한 위반이며 MVVM에 의해 지정된 아키텍처의 경계를 무너뜨립니다.
  • 대화 상자를 표시하려면 대화 상자를 표시하는 시기와 닫는 시기에 대한 지식이 필요합니다.
  • 대화상자를 표시하는 유일한 이유는 사용자와 대화하기 위해서이기 때문에 대화상자를 표시하려면 UI 및 사용자에 대해 알아야 합니다.
  • 대화 상자를 표시하려면 적절한 대화 상자 유형을 선택하려면 현재 UI 컨텍스트에 대한 지식이 필요합니다.
  • 하는 것이 .OpenFileDialog ★★★★★★★★★★★★★★★★★」UIElement이는 MVVM 패턴을 깨지만 뷰 모델 컴포넌트 또는 모델 컴포넌트에서의 UI 로직 구현 또는 참조(단, 이러한 의존성은 유용한 힌트가 될 수 있음)입니다.
  • 같은 이유로 모형 성분에서 대화 상자를 표시하는 것도 잘못될 수 있습니다.
  • UI 로직을 담당하는 유일한 구성 요소는 보기 구성 요소입니다.
  • MVVM의 관점에서 보면 C#, XAML, C++ 또는 VB와 같은 것은 없습니다.NET. 그 말은, 그 어떤 것도partial또는 관련 악명 높은 코드 배후에 있는 파일(*.xaml.cs)을 참조해 주세요.코드 배후의 개념은 컴파일러가 클래스의 XAML 부분을 C# 부분과 병합할 수 있도록 하기 위해 존재합니다.병합 후에는 두 파일이 모두 단일 클래스로 취급됩니다. 이는 순수한 컴파일러 개념입니다. partial는 XAML을 사용하여 클래스 코드를 기술할 수 있는 매직입니다(진정한 컴파일러 언어는 C# 또는 기타 IL 준거 언어입니다).
  • ICommand는 의 인터페이스입니다.NET 라이브러리는 MVVM에 관한 토픽이 아닙니다.모든 행동이 다른 사람들에 의해 촉발되어야 한다고 믿는 것은 잘못된 것이다.ICommand구현이 가능합니다.
    컴포넌트 간의 단방향 의존성이 유지되는 한 이벤트는 MVVM에 준거한 매우 유용한 개념입니다.항상 강제 사용ICommand사건을 이용하는 대신 작전본부에서 제시한 코드와 같은 부자연스럽고 냄새나는 코드를 발생시킵니다.
  • .ICommand뷰 모델 클래스에서만 구현해야 합니다.뷰 클래스에서도 구현할 수 있습니다.
    뷰는 으로 구현됩니다.RoutedCommand (오류)RoutedUICommand), ), 、 ), 、 쪽 、 장 、 장 、 장 の ), ), ), ), 。ICommand에서의 대화상자 표시를 트리거하기 위해서도 사용할 수 있습니다.Window다른 통제도 가능합니다.
    데이터 바인딩을 통해 UI가 뷰 모델과 데이터를 교환할 수 있습니다(데이터 소스 관점에서 익명으로)., 수 없기 WPF에서는 UWP가합니다), UPF(WPF)가 .ICommand ★★★★★★★★★★★★★★★★★」ICommandSource이 사실을 깨달았어요.
  • 일반적으로 인터페이스는 MVVM과 관련된 개념이 아닙니다.따라서 인터페이스의 도입(예:IFileDialogService)는 MVVM 관련 문제를 해결할 수 없습니다.
  • 서비스 또는 도우미 클래스는 MVVM의 개념이 아닙니다.따라서 서비스나 도우미 클래스를 도입해도 MVVM 관련 문제를 해결할 수 없습니다.
  • 클래스 이름 또는 유형 이름은 일반적으로 MVVM과 관련이 없습니다.뷰 모델 코드를 다른 클래스로 이동(클래스에 이름이 없거나 다음 이름이 붙지 않은 경우에도)ViewModelMVVM 관련 문제를 해결할 수 없습니다.

2.2 결론

해결책은 보기 구성 요소의 일부인 클래스의 사용자 상호 작용을 표시하는 것입니다.
즉, 이러한 클래스는 뷰 모델에서 알 수 없는 클래스여야 하므로 뷰 모델에서 호출할 수 없습니다.

이 로직은 코드 배후에 있는 파일에 직접 실장하거나 다른 클래스(파일) 내에 실장할 수 있습니다.구현은 단순한 도우미 클래스 또는 더 정교한(연결된) 동작일 수 있습니다.

요점은 UI 관련 로직을 포함하는 유일한 구성 요소이기 때문에 UI 구성 요소는 보기 구성 요소만으로 처리되어야 한다는 것입니다.뷰 모델은 뷰에 대한 지식이 없으므로 뷰와 통신하기 위해 능동적으로 작동할 수 없습니다.패시브 통신(데이터 바인딩, 이벤트)만 허용됩니다.

뷰를 통해 관찰할 수 있는 뷰 모델에 의해 발생한 이벤트를 사용하여 항상 특정 플로우를 구현하여 대화상자를 사용하여 사용자와 대화하는 등의 액션을 수행할 수 있습니다.

view-model-first 접근방식을 사용하는 솔루션이 있으며, 이는 애초에 MVVM을 위반하지 않습니다.그러나 잘못 설계된 책임도 이 솔루션을 안티패턴으로 만들 수 있습니다.

3 특정 대화상자의 요구를 수정하는 방법

대부분의 경우 애플리케이션 설계를 수정하면 애플리케이션 내에서 대화상자를 표시할 필요가 없습니다.

대화 상자는 사용자와의 상호작용을 가능하게 하는 UI 개념이기 때문에 UI 설계 규칙을 사용하여 대화 상자를 평가해야 합니다.
아마도 UI 디자인의 가장 유명한 규칙은 90년대에 닐슨과 몰리치가 가정한 10가지 규칙일 것이다.

하나의 중요한 규칙은 오류 예방에 관한 것입니다.그것은 다음과 같습니다.

관련된 . a) 입력과 관련된 오류는 방지해야 합니다.
및 않습니다.b) "사용자가 중단되는 것을 않습니다."

a)를 의미합니다.비즈니스 로직에 잘못된 데이터가 입력되지 않도록 합니다.
한 한 하지 않는 것을 의미합니다.b ) b b 、 b 、 b 、 b 、 b b b b b b 。응용 프로그램 내에서 대화 상자를 표시하지 말고 사용자가 마우스 클릭 시(예: 예기치 않은 중단 없음)에 대화 상자를 명시적으로 트리거하도록 하십시오.

이 간단한 규칙을 따르면 뷰 모델에 의해 트리거된 대화상자를 표시할 필요가 항상 없어집니다.

사용자의 관점에서 볼 때, 애플리케이션은 블랙박스입니다. 즉, 데이터를 출력하고, 데이터를 수신하고, 입력 데이터를 처리합니다.잘못된 데이터를 보호하기 위해 데이터 입력을 제어하면 정의되지 않은 상태 또는 잘못된 상태를 제거하고 데이터 무결성을 보장할 수 있습니다.즉, 애플리케이션 내부에서 사용자에게 대화상자를 표시할 필요가 없습니다.사용자에 의해 명시적으로 트리거된 것만 해당됩니다.

예를 들어, 일반적인 시나리오는 모델이 파일에 데이터를 유지해야 한다는 것입니다.대상 파일이 이미 존재하는 경우 사용자에게 이 파일을 덮어쓸지 확인하도록 요청합니다.

에러 방지 룰에 따라서, 유저는 항상 파일을 선택할 수 있습니다.이 파일이 소스 파일인지 대상 파일인지에 관계없이, 파일 다이얼로그에서 명시적으로 파일을 선택하는 것으로, 이 파일을 지정하는 것은 유저입니다.즉, 사용자는 예를 들어 "다른 이름으로 저장" 버튼을 클릭하여 파일 작업을 명시적으로 트리거해야 합니다.

이렇게 하면 파일 선택기 또는 파일 저장 대화 상자를 사용하여 기존 파일만 선택되도록 할 수 있습니다.또한 기존 파일 덮어쓰기에 대해 사용자에게 경고할 필요가 없습니다.

이 접근방식에 따라 a) "...모든 종류의 오류, 특히 입력과 관련된 오류를 방지" b) "...사용자는 오류 메시지와 대화상자에 의해 중단되는 것을 좋아하지 않습니다"만족시켰습니다.

갱신하다

대화 상자 뷰를 처리하기 위해 뷰 모델이 필요하지 않다는 사실에 대해 사람들이 의문을 제기하고 있기 때문에 요점을 입증하기 위해 데이터 검증과 같은 추가적인 "복잡한" 요건을 제시함으로써 이러한 복잡한 시나리오(처음에는 OP가 요청하지 않은)에 대처하기 위해 더 복잡한 예를 제시해야 합니다.

4 예

4.1 개요

을 수집한 후 를 하는 간단한 입니다.OpenFileDialog앨범 이름이 저장되는 대상 파일을 선택합니다.
3가지 심플한 솔루션:

해결책 1: 질문의 정확한 요건을 충족하는 매우 심플하고 기본적인 시나리오입니다.솔루션 2: 뷰 모델에서 데이터 검증을 사용할 수 있는 솔루션.예를 간단하게 하기 위해의 실장은 생략되어 있습니다.
솔루션 3: 또 하나의 우아한 솔루션으로ICommandICommandSource.CommandParameter대화 결과를 뷰 모델로 전송하고 지속성 작업을 실행합니다.

솔루션 1

하고 직관적인 으로 을 .OpenFileDialogMVVM에 준거한 방법으로.
이 솔루션을 사용하면 뷰 모델이 UI 구성 요소 또는 논리를 인식하지 못하는 상태를 유지할 수 있습니다.

해 볼 수 요.FileStream을 사용하다이렇게 하면 UI에서 직접 스트림을 생성하는 동안 필요한 경우 대화 상자를 표시하여 오류를 처리할 수 있습니다.

보다

Main Window.xaml

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>

  <StackPanel>
    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Show the file dialog. 
         Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            Click="SaveAlbumNameToFile_OnClick" />
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
    => InitializeComponent();

  private void SaveAlbumNameToFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();

    if (dialog.ShowDialog() == true)
    {
      // Consider to create the FileStream here to handle errors 
      // related to the user's picked file in the view. 
      // If opening the FileStream succeeds, we can pass it over to the viewmodel.
      string destinationFilePath = dialog.FileName;
      (this.DataContext as MainViewModel)?.SaveAlbumName(destinationFilePath);
    }
  }
}

모델 표시

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged
{
  // Raises PropertyChanged
  public string AlbumName { get; set; }
    
  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }
    
  public MainViewModel() => this.DataRepository = new DataRepository();
    
  // Since 'destinationFilePath' was picked using a file dialog, 
  // this method can't fail.
  public void SaveAlbumName(string destinationFilePath)
    => this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
}

솔루션 2

더의 '하다'를 추가하는 것입니다.TextBox카피&랩 경유로 행선지 파일 패스를 수집할 수 있도록, 입력 필드로 합니다.
★★★★★★★★★★★★★★★★★.TextBox는 뷰 있기 으로 구현되어 있습니다.이 클래스는 이상적인 방법으로 구현됩니다.INotifyDataErrorInfo이치노

추가 버튼을 누르면 옵션 파일 선택기 보기가 열리고 사용자가 파일 시스템을 탐색하여 대상을 선택할 수 있습니다.

마지막으로 "다른 이름으로 저장" 버튼으로 지속성 작업이 트리거됩니다.

보다

Main Window.xaml

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>    

  <StackPanel>

    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Alternative file path input, validated using INotifyDataErrorInfo validation 
         e.g. using File.Exists to validate the file path -->
    <TextBox x:Name="FilePathTextBox" 
             Text="{Binding DestinationPath, ValidatesOnNotifyDataErrors=True}" />

    <!-- Option to search a file using the file picker dialog -->
    <Button Content="Browse" Click="PickFile_OnClick" />

    <!-- Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            Command="{Binding SaveAlbumNameCommand}" />
  </StackPanel>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
  }

  private void PickFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();
    if (dialog.ShowDialog() == true)
    {
      this.FilePathTextBox.Text = dialog.FileName;

      // Since setting the property explicitly bypasses the data binding, 
      // we must explicitly update it by calling BindingExpression.UpdateSource()
      this.FilePathTextBox
        .GetBindingExpression(TextBox.TextProperty)
        .UpdateSource();
    }
  }
}

모델 표시

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
  private string albumName;
  public string AlbumName
  {
    get => this.albumName;
    set
    {
      this.albumName = value;
      OnPropertyChanged();
    }
  }

  private string destinationPath;
  public string DestinationPath
  {
    get => this.destinationPath;
    set
    {
      this.destinationPath = value;
      OnPropertyChanged();

      ValidateDestinationFilePath();
    }
  }

  public ICommand SaveAlbumNameCommand => new RelayCommand(
    commandParameter => ExecuteSaveAlbumName(this.TextValue),
    commandParameter => true);

  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }

  // Default constructor
  public MainViewModel() => this.DataRepository = new DataRepository();

  private void ExecuteSaveAlbumName(string destinationFilePath)
  {
    // Use a aggregated/composed model class to persist the data
    this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
  }
}

솔루션 3

다음 솔루션은 두 번째 시나리오의 보다 우아한 버전입니다., 이렇게 요.ICommandSource.CommandParameter대화상자 결과를 뷰 모델로 전송하는 속성(이전 예에서 사용된 데이터 바인딩의 속성)입니다.
옵션의 사용자 입력(복사 및 붙여넣기 등)의 검증은 바인딩 검증을 사용하여 검증됩니다.

보다

Main Window.xaml

<Window x:Name="Window">
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext> 

  <StackPanel> 

    <!-- The data to persist -->
    <TextBox Text="{Binding AlbumName}" />

    <!-- Alternative file path input, validated using binding validation 
         e.g. using File.Exists to validate the file path -->
    <TextBox x:Name="FilePathTextBox">
      <TextBox.Text>
        <Binding ElementName="Window" Path="DestinationPath">
          <Binding.ValidationRules>
            <FilePathValidationRule />
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>

    <!-- Option to search a file using the file picker dialog -->
    <Button Content="Browse" Click="PickFile_OnClick" />

    <!-- Let user explicitly trigger the file save operation. 
         This button will be disabled until the required input is valid -->
    <Button Content="Save as" 
            CommandParameter="{Binding ElementName=Window, Path=DestinationPath}" 
            Command="{Binding SaveAlbumNameCommand}" />
  </StackPanel>
</Window>

FilePathValidationRule.cs

class FilePathValidationRule : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    => value is string filePath && File.Exists(filePath)
      ? ValidationResult.ValidResult
      : new ValidationResult(false, "File path does not exist.");
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty DestinationPathProperty = DependencyProperty.Register(
    "DestinationPath",
    typeof(string),
    typeof(MainWindow),
    new PropertyMetadata(default(string)));

  public string DestinationPath
  {
    get => (string)GetValue(MainWindow.DestinationPathProperty);
    set => SetValue(MainWindow.DestinationPathProperty, value);
  }

  public MainWindow()
  {
    InitializeComponent();
  }

  private void PickFile_OnClick(object sender, EventArgs e)
  {
    var dialog = new OpenFileDialog();
    if (dialog.ShowDialog() == true)
    {
      this.DestinationPath = dialog.FileName;
    }
  }
}

모델 표시

MainViewModel.cs

class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
  private string albumName;
  public string AlbumName
  {
    get => this.albumName;
    set
    {
      this.albumName = value;
      OnPropertyChanged();
    }
  }

  public ICommand SaveAlbumNameCommand => new RelayCommand(
    commandParameter => ExecuteSaveAlbumName(commandParameter as string),
    commandParameter => true);

  // A model class that is responsible to persist and load data
  private DataRepository DataRepository { get; }

  // Default constructor
  public MainViewModel() => this.DataRepository = new DataRepository();

  private void ExecuteSaveAlbumName(string destinationFilePath)
  {
    // Use a aggregated/composed model class to persist the data
    this.DataRepository.SaveData(this.AlbumName, destinationFilePath);
  }
}

여기서 가장 좋은 것은 서비스를 이용하는 것이다.

서비스는 중앙 서비스 저장소(종종 IOC 컨테이너)에서 액세스하는 클래스입니다.그런 다음 OpenFileDialog와 같은 필요한 기능을 구현합니다.

만약에 '이렇게'가 가정하면 '이렇게'를IFileDialogService유니티 컨테이너에서는, 다음의 작업을 할 수 있습니다.

void Browse(object param)
{
    var fileDialogService = container.Resolve<IFileDialogService>();

    string path = fileDialogService.OpenFileDialog();

    if (!string.IsNullOrEmpty(path))
    {
        //Do stuff
    }
}

답변 중 하나에 대해 코멘트를 하고 싶었지만, 안타깝게도 그렇게 할 만큼 제 평판은 높지 않습니다.

OpenFileDialog() 등의 콜이 있으면 뷰 모델 내의 뷰(대화상자)를 의미하기 때문에 MVVM 패턴을 위반합니다.뷰 모델은 GetFileName()과 같은 것을 호출할 수 있지만(즉, 단순한 바인딩이 충분하지 않은 경우), 파일 이름을 얻는 방법은 신경 쓰지 않습니다.

ViewModel은 대화상자를 열거나 대화상자의 존재를 알 수 없습니다.VM이 다른 DLL에 저장되어 있는 경우 프로젝트에는 Presentation Framework에 대한 참조가 없어야 합니다.

뷰의 도우미 클래스를 일반적인 대화 상자에 사용합니다.

도우미 클래스는 XAML에서 창이 바인딩되는 명령어(이벤트가 아님)를 표시합니다.이것은 뷰 내에서 RelayCommand를 사용하는 것을 의미합니다.도우미 클래스는 DepensyObject이므로 뷰 모델에 바인딩할 수 있습니다.

class DialogHelper : DependencyObject
{
    public ViewModel ViewModel
    {
        get { return (ViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(ViewModelProperty_Changed)));

    private static void ViewModelProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (ViewModelProperty != null)
        {
            Binding myBinding = new Binding("FileName");
            myBinding.Source = e.NewValue;
            myBinding.Mode = BindingMode.OneWayToSource;
            BindingOperations.SetBinding(d, FileNameProperty, myBinding);
        }
    }

    private string FileName
    {
        get { return (string)GetValue(FileNameProperty); }
        set { SetValue(FileNameProperty, value); }
    }

    private static readonly DependencyProperty FileNameProperty =
        DependencyProperty.Register("FileName", typeof(string), typeof(DialogHelper),
        new UIPropertyMetadata(new PropertyChangedCallback(FileNameProperty_Changed)));

    private static void FileNameProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("DialogHelper.FileName = {0}", e.NewValue);
    }

    public ICommand OpenFile { get; private set; }

    public DialogHelper()
    {
        OpenFile = new RelayCommand(OpenFileAction);
    }

    private void OpenFileAction(object obj)
    {
        OpenFileDialog dlg = new OpenFileDialog();

        if (dlg.ShowDialog() == true)
        {
            FileName = dlg.FileName;
        }
    }
}

도우미 클래스에는 ViewModel 인스턴스에 대한 참조가 필요합니다.리소스 사전을 참조하십시오.구축 직후에는 (XAML의 같은 행에) ViewModel 속성이 설정됩니다.이 경우 도우미 클래스의 FileName 속성이 뷰 모델의 FileName 속성에 바인딩됩니다.

<Window x:Class="DialogExperiment.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DialogExperiment"
        xmlns:vm="clr-namespace:DialogExperimentVM;assembly=DialogExperimentVM"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <vm:ViewModel x:Key="viewModel" />
        <local:DialogHelper x:Key="helper" ViewModel="{StaticResource viewModel}"/>
    </Window.Resources>
    <DockPanel DataContext="{StaticResource viewModel}">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Open" Command="{Binding Source={StaticResource helper}, Path=OpenFile}" />
            </MenuItem>
        </Menu>
    </DockPanel>
</Window>

예를 들어, viewModel의 컨스트럭터에 전달하거나 종속성 주입을 통해 해결할 수 있는 서비스를 사용합니다.

public interface IOpenFileService
{
    string FileName { get; }
    bool OpenFileDialog()
}

또한 Open FileDialog를 사용하여 이를 구현하는 클래스도 있습니다.view Model에서는 인터페이스만 사용하기 때문에 필요에 따라 mock/replace할 수 있습니다.

저는 이 문제를 다음과 같이 해결했습니다.

  • ViewModel에서 인터페이스를 정의하고 ViewModel에서 인터페이스를 조작합니다.
  • 인터페이스는 View에 구현되어 있습니다.

CommandImpl은 다음 코드에서는 구현되지 않습니다.

뷰 모델:

namespace ViewModels.Interfaces
{
    using System.Collections.Generic;
    public interface IDialogWindow
    {
        List<string> ExecuteFileDialog(object owner, string extFilter);
    }
}

namespace ViewModels
{
    using ViewModels.Interfaces;
    public class MyViewModel
    {
        public ICommand DoSomeThingCmd { get; } = new CommandImpl((dialogType) =>
        {
            var dlgObj = Activator.CreateInstance(dialogType) as IDialogWindow;
            var fileNames = dlgObj?.ExecuteFileDialog(null, "*.txt");
            //Do something with fileNames..
        });
    }
}

표시:

namespace Views
{
    using ViewModels.Interfaces;
    using Microsoft.Win32;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;

    public class OpenFilesDialog : IDialogWindow
    {
        public List<string> ExecuteFileDialog(object owner, string extFilter)
        {
            var fd = new OpenFileDialog();
            fd.Multiselect = true;
            if (!string.IsNullOrWhiteSpace(extFilter))
            {
                fd.Filter = extFilter;
            }
            fd.ShowDialog(owner as Window);

            return fd.FileNames.ToList();
        }
    }
}

XAML:

<Window xmlns:views="clr-namespace:Views"
        xmlns:viewModels="clr-namespace:ViewModels">    
    <Window.DataContext>
        <viewModels:MyViewModel/>
    </Window.DataContext>
    <Grid>
        <Button Content = "Open files.." Command="{Binding DoSomeThingCmd}"
                CommandParameter="{x:Type views:OpenFilesDialog}"/>
    </Grid>
</Window>

서비스를 제공한다는 것은 뷰 모델에서 뷰를 여는 것과 같습니다.종속성 속성이 표시되므로 속성 변경 시 FileDialog를 열고 경로를 읽고 속성을 업데이트하여 VM의 바인딩 속성을 업데이트합니다.

언급URL : https://stackoverflow.com/questions/1043918/open-file-dialog-mvvm

반응형