2014-01-25 2 views
3

WPF 응용 프로그램에서 "JQuery"스타일 회 전자를 구현하려고합니다. 이 회 전자는 일부 별도의 PRISM 모듈에서 트리거 될 것이므로 회 전자가 MainWindow의 속성에 액세스하는 데 필요한 전체 응용 프로그램 창을 커버해야합니다.별도의 MVVM 모듈에서 MainWindow 속성 업데이트

MainWindow에 속성이 있지만 다른 모듈에서 볼 수 없습니다.

나는 Application.Current.MainWindow를 시도했지만 운이 없다.

또한 Application.Current.Properties []를 사용해 보았지만 OnPropertyChanged 이벤트를 트리거하는 방법을 알지 못합니다.

누군가 올바른 방향으로 나를 가리킬 수 있습니까?

업데이트 : 다음은 몇 가지 스크린 샷과 내가하고 싶은 것에 대한 더 나은 설명입니다.

다음은 내 문제의 예입니다.

  • WPFApp.Module4
  • WPFApp MainWindow를 포함를

    • WPFApp
    • WPFApp.Module1
    • WPFApp.Module2
    • WPFApp.Module3 : 나는 다음을 포함하는 응용 프로그램이 2 영역, 메뉴 영역 (왼편) 및 컨텐츠 영역. 의 각각에서 MainWindow를 컨텐츠 영역

    에로드되는 MainWindow를 메뉴 영역

  • 콘텐츠보기에로드

    • 메뉴보기 : 각 모듈은 2 뷰를 포함 모듈 내용보기 몇 초가 걸리는 작업을 수행하려고하며 작업이 수행되는 동안 전체 응용 프로그램 창을 포괄하는 "Ajax 스타일"회 전자를 보여주고 싶습니다. 여기에 설명 된 스피너 클래스를 사용하려면 : WPF Spinner via MVVM & Attached Properties 위의 링크에서 자세히 설명 된 AsyncNotifier.Trigger를 각 모듈 컨텐츠 뷰에 ​​추가하여이 기능을 사용할 수있었습니다 (아래 참조).

      enter image description here enter image description here

      내 문제는 : 나는 회 전체 응용 프로그램 창을 포함 할 경우 나는 MainWindow를에 AsyncNotifier.Trigger를 추가해야합니다. MainWindow에서 스피너를 보여줄 책임이있는 속성을 노출하고 각 모듈에서이 속성에 액세스 할 수 있어야합니다. 이 방법에 대한 아이디어가 있으십니까?

      는 업데이트 :

      좋아, 나는 여전히 조금 더 받고있을 것 같아 조금 모두가 함께 맞는 방법에 붙어.

      다른 인터페이스가 액세스 할 수 있도록 내 인터페이스를 만들어 인프라 모듈에 넣었습니다.

      다음 코드를 사용하여 AggregateModuleCatalog 클래스에서 모듈을로드하고 있습니다.

      /// <summary> 
      /// this.Catalogs is a readonly collection of IModuleCatalog 
      /// Initializes the catalog, which may load and validate the modules. 
      /// </summary> 
      public void Initialize() 
      { 
          foreach (var catalog in this.Catalogs) 
          { 
           catalog.Initialize(); 
          } 
      } 
      

      내 문제

      내가이 SpinnerViewModel을 넣어해야하는 위치의 확실하지 오전입니까? 내 메인 프로젝트에 있어야할까요?

      또한 Constructor 삽입을 사용하여 싱글 톤을 통과해야합니까?

  • 답변

    3

    여기서 중개자 패턴을 구현하는 방법을 살펴 보겠습니다. 이것은 당신이 어떤 가입자에게 특정 유형의 제어 메시지를 게시 할 것처럼 소리, 기본적으로 EventAggregator

    의 형태로 프리즘에 이미 존재하는 -이 경우 당신이 :

    • 구독자
      • MainWindow를
    • 발행인
      • 모듈 1
      • 모듈 2
      • 모듈은 메시지를 게시해야 3

    모듈들은/모듈 작업 하다니 MainWindow이 완료되고에 '바쁜'원하는 신호는 등

    에게로드입니다 중요한 점은 어떤 식 으로든 MainWindow을 인식해서는 안된다는 것입니다. 이렇게하면 솔루션 분리, MVVM 친화, 재사용 및 유지 관리가 쉽습니다. 이 방법은 일반적으로 좋은 생각 인 종속성 주입을 사용합니다.

    이 작업을 수행하려면 모듈이 EventAggregator 서비스에 종속되어야합니다 (서비스 인터페이스 인 PRISM에는 IEventAggregator 인터페이스가 있습니다) 메시지를 게시해야합니다.

    먼저 메시지 클래스로 사용할 이벤트가 필요합니다. 분명히 이것은 게시자와 구독자 모두에게 보여야하기 때문에 외부 참조에 앉아 있어야 할 수도 있습니다. 이 메시지는 CompositeWpfEvent에서 상속되어야하며 메시지 페이로드에 대한 일반적인 인수를 제공해야합니다.

    public class BusyUserInterfaceEvent : CompositeWpfEvent<bool> 
    { 
    } 
    

    그런

    당신이 필요로하는 (난 당신이 단지 바로 똑같이 잘 작동하지만 당신은 페이로드로 부울을 사용하는 이상의 이벤트 유형을 포함하기로 결정한 경우 충분히 특정되지 수있는 이것에 대한 CompositeWpfEvent<bool>을 사용할 수 있다고 가정) 모듈

    public class Module1 
    { 
        private IEventAggregator _eventAggregator; 
    
        public Module1(IEventAggregator eventAggregator) 
        { 
         _eventAggregator = eventAggregator; 
        } 
    
        public DoSomeWork() 
        { 
         // Busy the UI by publishing the above event 
         _eventAggregator.GetEvent<BusyUserInterfaceEvent>().Publish(true); 
        } 
    
        public FinishDoingSomeWork() 
        { 
         // Unbusy the UI by publishing the above event with 'false' 
         _eventAggregator.GetEvent<BusyUserInterfaceEvent>().Publish(false); 
        } 
    } 
    

    에서 위의 유형의 이벤트를 게시 할 수있는 MainWindow 또한 EventAggregator 서비스에 대한 종속성을해야하고 특정 유형의 메시지에 가입해야합니다. 작품은 (는이 경우에서와 같이) UI 스레드에서 수행 할 경우 내가 이전에 성공적으로 프레임 워크의 몇이 패턴을 사용했다 동안 당신이 ThreadOption.UIThread

    public class MainWindow 
    { 
        private IEventAggregator _eventAggregator; 
    
        public MainWindow(IEventAggregator eventAggregator) 
        { 
         _eventAggregator = eventAggregator; 
    
         // Subscribe to any messages of the defined type on the UI thread 
         // The BusyUserInterface method will handle the event 
         _eventAggregator.GetEvent<BusyUserInterfaceEvent>().Subscribe(BusyUserInterface, ThreadOption.UIThread); 
        } 
    
        public BusyUserInterface(bool busy) 
        { 
         // Toggle the UI - pseudocode here! 
         TickerActive = busy; 
        } 
    } 
    

    를 사용하여 가입해야합니다, 나는 '그 나는 천국을 인정 이 패턴을 사용하여

    http://msdn.microsoft.com/en-us/library/ff921122.aspx

    이 어떤 구성 요소를 분리한다,는 이벤트 및 구독에서 MainWindow을 중지 쉽게 있도록 : 위의 코드는하지만, 문서가 간결 것, 꽤 괜찮되지 않을 수도 있습니다, 그래서 t는 PRISM을 사용 필요없는 경우 구독자를 상위 구성 요소로 이동 게시자의 구현을 지원합니다.

    이것은 완전히 다른 창에 대해 MainWindow을 교환하고 응용 프로그램 수명주기의 일부 동안 모듈을로드 할 수 있으며 이러한 이벤트 유형을 구독하는 동안 메시지를 기반으로 여전히 바쁘거나 불쾌 할 수 있음을 의미합니다. 모듈로부터 퍼블리싱. 매우 유연한 솔루션입니다.

    PRISM 구현은 또한 구독 필터링을 제공하므로 선택적으로 이벤트를 수신 할 수 있습니다.

    또한 이벤트를 집계하기 때문에 모듈 간 통신을 허용하기 위해이 작업을 많이 수행 할 수 있습니다. 모든 사람들이 메시지 유형을 알고 있는지 확인해야합니다. 따라서 메시지 유형을 보유하는 데 외부 종속성이 필요할 수 있습니다.

    +0

    이 패턴을 회 전자 UI 요소의 모든 곳에서 사용합니다.이 패턴은 내가 사용하는 코드와 매우 유사합니다. 그러나 EventAggregator가 더 많은 전역 범위를 사용해서는 안됩니까? –

    +1

    하위 요소에서 컨트롤 트리의 맨 위에있는 요소를 사용 중이라는 메시지가 전역 범위에서 어떤 것인지 확실하지 않은 것은 이벤트 집계가 수행하는 구성 요소 간 통신을 분리 한 것입니다. 너무 제한된 범위라고는 생각하지 않습니다. 사용자가 자신 만의 이벤트 유형을 만들므로 메시지가 구성 요소와 상호 작용하는 수준을 결정할 수 있습니다. – Charleh

    +1

    @Charleh, 매력처럼 일했습니다. 고마워요. 정말 고마워요. – Nollaig

    0

    회자의 "시각적"스타일/인터페이스를 더 많이 만들려면 고유 한 하위 클래스 회 전자 (특수 기능이 필요한 경우)를 만든 다음 자신 만의 맞춤 스타일을 만드십시오. 그것의보기 또는 행동. 그런 다음 회 전자를 양식에 붙이면 지정하는 클래스/스타일의 회 전자를 사용하면 잘 작동합니다.

    Here is one of my own learning about styles, but simple label

    Another link where I helped someone walking through creating their own custom class/style which MIGHT be what you are trying to globally implement.

    And a link to template/style defaults to learn from

    +0

    감사합니다. 스피너 클래스와 스타일이 이미 설정되어 있고 메인 윈도우에 배치되어 있고 MainWindow ViewModel의 속성에 바인딩되어 있지만 다른 모듈에서이 속성을 업데이트 할 수 있기를 기대하고 있습니다. – Nollaig

    +0

    좀 더 설명해 드리겠습니다. MainWindow에는 2 개의 영역, 메뉴 및 내용이 있습니다. 전체 응용 프로그램이 회 전자에 의해 덮 이도록 회 전자가로드되는 곳이기도합니다. 각 모듈에는 컨텐츠 영역에 대한보기뿐만 아니라 메뉴 영역에 대한보기가 있습니다. 모듈의 컨텐츠보기에서 액션에 의해 스피너가 트리거되기를 원합니다. 문제는로드 할 스피너를 트리거하기 위해 각 모듈 컨텐츠 viewmodels에서 액세스 할 수있는 MainWindow에서 속성을 설정하는 방법을 모르겠다는 것입니다. 도움이 될 것입니다. – Nollaig

    +0

    @ Nollaig, 인쇄 화면/샘플을 붙이거나 (일부분 축소해도) 게시물을 추가하여 더 명확하게 볼 수 있다면 도움이 될 것입니다. 그런 다음 댓글/설명을 다시 추가하십시오. 원래 게시물 ... 중간에, 나는 당신이 찾고있는 것을 상상하려고 노력할 것입니다. – DRapp

    2

    이 나는 ​​뭔가를 놓치고 있습니까? 이것은 이것처럼 쉬워야할까요?

    App.xaml.cs를

    public partial class App 
    { 
        public static Window MainWindow {get;set;} 
    } 
    

    MainWindow.xaml.cs를

    public MainWindow() 
    { 
        App.MainWindow = this; 
        InitializeComponent(); 
    } 
    public void Spinner(bool show) 
    { 
        // Your code here 
    } 
    

    지금 MVVM에, 아이디어는 그것을하지 않는다, 같은 것 코드 뒤에. 바인딩 할 회 전자에 대한 싱글 톤 (Singleton은 정적 인스턴스를 가짐)을 만들 수 있으며, 그러면 모든 모듈이 싱글 톤 속성으로 엉망이 될 수 있습니다.

    종속성 삽입이 마음에 들면 인터페이스를 만들고 해당 인터페이스를 구현하는 객체를 만들고 Spinner를 해당 객체에 바인딩하십시오. 그런 다음 해당 객체가로드 될 때 모듈에 인터페이스로 삽입하십시오.

    다음은 인터페이스를 구현하는 싱글 톤 ViewModel입니다. 프리즘 프로젝트는 인터페이스에 대해서만 알아야합니다. 그러나 프리즘 모듈을로드하는 코드는 인터페이스와 싱글 톤에 대해 알아야합니다. 그런 다음 프리즘 모듈이로드되면 싱글 톤이 전달됩니다 (생성자 주입, 메소드 주입 또는 속성 주입 중 가장 쉬운 주입 방법을 자유롭게 사용하십시오).

    using System.ComponentModel; 
    
    namespace ExampleCode 
    { 
        /// <summary> 
        /// Interface for managing a spinner 
        /// </summary> 
        public interface IManageASpinner 
        { 
         bool IsVisible {get;set;} 
         // Add any other properties or methods you might need. 
        } 
    
        /// <summary> 
        /// A singleton spinner ViewModel for the main window spiner 
        /// </summary> 
        public class SpinnerViewModel : INotifyPropertyChanged, IManageASpinner 
        {  #region Singleton and Constructor 
         /// <summary> 
         /// Singleton instance 
         /// </summary> 
         public static SpinnerViewModel Instance 
         { 
          get { return _Instance ?? (_Instance = new SpinnerViewModel()); } 
         } private static SpinnerViewModel _Instance; 
    
         /// <summary> 
         /// Private constructor to prevent multiple instances 
         /// </summary> 
         private SpinnerViewModel() 
         { 
         } 
         #endregion 
    
         #region Properties 
         /// <summary> 
         /// Is the spinner visible or not? 
         /// Xaml Binding: {Binding Source={x:Static svm:SpinnerViewModel.Instance}, Path=IsVisible} 
         /// </summary> 
         public bool IsVisible 
         { 
          get { return _IsVisible; } 
          set 
          { 
           _IsVisible = value; 
           OnPropertyChanged("IsVisible"); 
          } 
         } private bool _IsVisible; 
    
         // Add any other properties you might want to include 
         // such as IsSpinning, etc.. 
    
         #endregion 
    
         #region INotifyPropertyChanged implementation 
         public event PropertyChangedEventHandler PropertyChanged; 
    
         public void OnPropertyChanged(string name) 
         { 
          var handler = PropertyChanged; 
          if (handler != null) 
          { 
           handler(this, new PropertyChangedEventArgs(name)); 
          } 
         } 
         #endregion 
        } 
    } 
    

    당신이 여기에서 점들을 연결 수 있기를 바랍니다.

    추가 이월 12

    아마 방법 주입을 사용합니까? 그래도 할 수 있을지 확신하지 못합니다. IModuleCatalog 인터페이스 또는 Catalog 클래스를 편집 할 수 있습니까? 그렇지 않은 경우에는 건너 뛰고 다음 아이디어로 이동하십시오.

    foreach (var catalog in this.Catalogs) 
    { 
        catalog.Initialize(SpinnerViewModel.Instance); 
    } 
    

    아마도 카탈로그는 두 번째 인터페이스를 구현하고 생성자 주입 대신 속성 주입을 사용해야합니다.

    public interface IHaveASpinner 
    { 
        public IManageASpinner Spinner {get;set;} 
    } 
    

    지금, IModuleCatalog도 IHaveASpinner를 구현하는 구현의 모든 카탈로그를 가지고있다.

    foreach (var catalog in this.Catalogs) 
    { 
        catalog.Initialize(); 
        var needSpinner = catalog as IHaveASpinner; 
        if (needSpinner != null) 
        { 
         needSpinner.Spinner = SpinnerViewModel.Instance; 
        } 
    } 
    

    나는 SpinnerViewModel을 두어야 장소 :

    그런 다음 코드는이 작업을 수행해야?

    주 프로젝트를 제안합니다. 그것은 당신의 디자인에 달려 있습니다. 모든 모듈이 이미 기본 프로젝트를 참조합니까? 그렇다면 기본 프로젝트에 넣으십시오. 그렇지 않다면 아마도 메인 프로젝트와 모든 모듈이 이미 참조한 프로젝트가있을 것입니다. 당신은 비록 당신이 무엇을 참조 할 수 있는지에 대한 규칙을 가진 엄격한 디자인을 가지고 있지 않다면 당신의 스피너에 대해 별도의 프로젝트/dll을 만들 수도 있습니다.

    +0

    은이 문제를 해결하기 위해 Dependency Injection을 사용하는 방법을 보여줍니다. – Nollaig

    +0

    음, 예제 인터페이스와 인터페이스를 구현하는 싱글 톤 ViewModel을 작성했습니다. 싱글 톤 속성에 바인딩하는 방법은 ViewModel의 주석을 참조하십시오. – Rhyous

    +0

    위에 추가 된 업데이트를 참조하십시오. – Nollaig

    1

    스피너에만 VM이 필요하지 않습니다. 내가 뭘하는 내 메인 윈도우의 관점에서 (애니메이션, 아이콘, 무엇이든) 스피너 자체가, 그리고 메인 윈도우는 VM은 다음과 같은 인터페이스를 구현해야 : 좋아하는 IoC 컨테이너 (나는 성을 사용)를 사용하여

    public interface IApplicationBusyIndicator 
    { 
        bool IsBusy { get; set; } 
    } 
    

    을, IApplicationBusyIndicator를 다른 VM에 삽입하기 만하면됩니다. 이들 중 하나가 장기 실행 작업을 시작할 때마다 IsBusy를 true로 설정합니다 (완료되면 false로 되돌립니다). 주 창보기의 통화 중 표시기에는 주 창 VM IsBusy 속성에 연결된 Visibility 속성이 있습니다 (물론 부울 - 가시성 변환기 사용).

    다른 모듈의 VM에 기본 창 VM의 인스턴스가 삽입되지 않도록 일부 아키텍처 제약이있는 경우 Prism의 이벤트 수집기를 사용하여 "응용 프로그램 사용 중"이라고 말할 수 있습니다. 메인 윈도우 VM이 이벤트에 가입하도록하고 비지 표시자를 표시합니다.

    관련 문제