مشاريع - مكتبة اكواد c#




كيف يجب على ShowModel إغلاق النموذج؟ (17)

اعتدت على السلوكيات المرفقة لإغلاق النافذة. ربط خاصية "إشارة" على ViewModel بالسلوك المرفق (أستخدم فعلا مشغل) عند ضبطه على true ، يقوم السلوك بإغلاق النافذة.

http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/

أحاول تعلم WPF ومشكلة MVVM ، ولكن قد بلغ عقبة. هذا السؤال مشابه ولكن ليس تماماً مثل هذا السؤال (التعامل مع الحوارات في wpf-with-mvvm) ...

لدي نموذج "تسجيل الدخول" مكتوبًا باستخدام نمط MVVM.

يحتوي هذا النموذج على ViewModel التي تحمل اسم المستخدم وكلمة المرور ، والتي ترتبط بالعرض في XAML باستخدام روابط البيانات العادية. كما أن لديها أمر "تسجيل الدخول" الذي يرتبط بزر "تسجيل الدخول" في النموذج ، agan باستخدام ربط البيانات العادي.

عند إطلاق الأمر "تسجيل الدخول" ، فإنه يستدعي وظيفة في ViewModel التي تنطلق وترسل البيانات عبر الشبكة لتسجيل الدخول. عند اكتمال هذه الوظيفة ، هناك إجراءان:

  1. كان تسجيل الدخول غير صالح - نحن فقط نعرض MessageBox وكل شيء على ما يرام

  2. كان تسجيل الدخول صالحًا ، نحتاج إلى إغلاق نموذج تسجيل الدخول وجعله صحيحًا نظرًا لـ DialogResult ...

المشكلة هي أن ViewModel لا يعرف شيئًا عن العرض الفعلي ، لذا كيف يمكنه إغلاق العرض وإخباره بإرجاع DialogResult معين؟ يمكنني التمسك ببعض التعليمات البرمجية في CodeBehind ، و / أو تمرير العرض من خلال ViewModel ، ولكن يبدو أنه سيهزم نقطة كاملة من MVVM تماما ...

تحديث

في النهاية ، انتهكت للتو "نقاء" نمط MVVM وكان عرض نشر حدث Closed ، وفضح طريقة Close . ثم كان view.Close مجرد استدعاء view.Close . لا يُعرف العرض إلا من خلال واجهة ويتم توصيله عبر حاوية IOC ، لذلك لا يتم فقدان قابلية الاختبار أو الصيانة.

يبدو سخيفًا أن الإجابة المقبولة عند -5 أصوات! بينما أنا على دراية جيدة بالمشاعر الجيدة التي يحصل عليها المرء عن طريق حل مشكلة بينما يكون "محض" ، بالتأكيد أنا لست الوحيد الذي يعتقد أن 200 سطر من الأحداث والأوامر والسلوكيات فقط لتجنب طريقة سطر واحد في اسم "أنماط" و "نقاء" هو أمر مثير للسخرية قليلا ....


الطريقة التي يمكنني التعامل معها هي إضافة معالج أحداث في برنامجي ViewModel. عندما تم تسجيل دخول المستخدم بنجاح سأطلق الحدث. في طريقة العرض الخاصة بي ، أود أن أعلق على هذا الحدث وعندما أطلقت أطلقت أغلق النافذة.


بافتراض أن مربع حوار تسجيل الدخول الخاص بك هو الإطار الأول الذي يتم إنشاؤه ، جرّب ذلك داخل فئة LoginViewModel:

    void OnLoginResponse(bool loginSucceded)
    {
        if (loginSucceded)
        {
            Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
            window.Show();

            App.Current.MainWindow.Close();
            App.Current.MainWindow = window;
        }
        else
        {
            LoginError = true;
        }
    }

حل آخر هو إنشاء خاصية مع INotifyPropertyChanged في View Model مثل DialogResult ثم كتابة التعليمات البرمجية في ما بعد:

public class SomeWindow: ChildWindow
{
    private SomeViewModel _someViewModel;

    public SomeWindow()
    {
        InitializeComponent();

        this.Loaded += SomeWindow_Loaded;
        this.Closed += SomeWindow_Closed;
    }

    void SomeWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _someViewModel = this.DataContext as SomeViewModel;
        _someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
    }

    void SomeWindow_Closed(object sender, System.EventArgs e)
    {
        _someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
        this.Loaded -= SomeWindow_Loaded;
        this.Closed -= SomeWindow_Closed;
    }

    void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
        {
            this.DialogResult = _someViewModel.DialogResult;
        }
    }
}

الجزء الأكثر أهمية هو _someViewModel_PropertyChanged . يمكن أن يكون SomeViewModel بعض سلسلة SomeViewModel العامة في SomeViewModel .

أستخدم هذا النوع من الخدع لإجراء بعض التغييرات في عناصر التحكم في العرض عندما يكون من الصعب القيام بذلك في ViewModel. OnPropertyChanged في ViewModel يمكنك القيام بأي شيء تريده في عرض. لا يزال ViewModel "وحدة قابلة للاختبار" وبعض خطوط صغيرة من التعليمات البرمجية في التعليمات البرمجية الخلفية لا يُغير.


سأذهب بهذه الطريقة:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;    
using GalaSoft.MvvmLight.Messaging; 

// View

public partial class TestCloseWindow : Window
{
    public TestCloseWindow() {
        InitializeComponent();
        Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
    }
}

// View Model

public class MainViewModel: ViewModelBase
{
    ICommand _closeChildWindowCommand;

    public ICommand CloseChildWindowCommand {
        get {
            return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
                Messenger.Default.Send(new CloseWindowMsg());
        }));
        }
    }
}

public class CloseWindowMsg
{
}

عندما تحتاج إلى إغلاق النافذة ، ببساطة ضعها في عارض المشاهد:

تا-دا

  foreach (Window window in Application.Current.Windows)
        {
            if (window.DataContext == this)
            {
                window.Close();
                return;
            }
        }

في حين أن هذا لا يجيب على السؤال عن كيفية القيام بذلك عبر عارض العرض ، فإن هذا يوضح كيفية القيام بذلك باستخدام XAML + المزج SDK فقط.

اخترت تحميل واستخدم ملفين من Blend SDK ، وكلاهما يمكنك الحصول على حزمة من Microsoft من خلال NuGet. الملفات هي:

System.Windows.Interactivity.dll و Microsoft.Expression.Interactions.dll

يعطيك Microsoft.Expression.Interactions.dll إمكانيات جيدة مثل القدرة على تعيين الخاصية أو استدعاء أسلوب على viewmodel أو هدف آخر ولديه أدوات أخرى داخل كذلك.

بعض XAML:

<Window x:Class="Blah.Blah.MyWindow"
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
  ...>
 <StackPanel>
    <Button x:Name="OKButton" Content="OK">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="True"
                      IsEnabled="{Binding SomeBoolOnTheVM}" />                                
          </i:EventTrigger>
    </Button>
    <Button x:Name="CancelButton" Content="Cancel">
       <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
             <ei:ChangePropertyAction
                      TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                      PropertyName="DialogResult"
                      Value="False" />                                
          </i:EventTrigger>
    </Button>

    <Button x:Name="CloseButton" Content="Close">
       <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <!-- method being invoked should be void w/ no args -->
                    <ei:CallMethodAction
                        TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                        MethodName="Close" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
    </Button>
 <StackPanel>
</Window>

Note that if you're just going for simple OK/Cancel behavior, you can get away w/ using the IsDefault and IsCancel properties as long as the window is shown w/ Window.ShowDialog().
I personally had problems w/ a button that had the IsDefault property set to true, but it was hidden when the page is loaded. It didn't seem to want to play nicely after it was shown, so I just am setting the Window.DialogResult property as shown above instead and it works for me.


قمت بتطبيق حل Joe White ، ولكن واجهت مشاكل مع " DialogResult العرضية يمكن تعيين فقط بعد أن يتم إنشاء إطار وتظهر كـ مربع حوار ".

كنت أحتفظ بـ ViewModel بعد إغلاق "عرض" وأحيانًا قمت بفتح "عرض جديد" في وقت لاحق باستخدام نفس جهاز VM. يبدو أن إغلاق طريقة العرض الجديدة قبل العرض القديم تم جمع البيانات المهملة الناتجة في DialogResultChanged محاولة تعيين الخاصية DialogResult على الإطار المغلق ، وبالتالي إثارة الخطأ.

كان الحل الخاص بي لتغيير DialogResultChanged للتحقق من خاصية IsLoaded الإطار:

private static void DialogResultChanged(
    DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    var window = d as Window;
    if (window != null && window.IsLoaded)
        window.DialogResult = e.NewValue as bool?;
}

بعد إجراء هذا التغيير ، يتم تجاهل أي مرفقات بمربعات حوار مغلقة.


لقد قرأت جميع الإجابات ولكن يجب أن أقول أن معظمها ليس جيدًا بما فيه الكفاية أو حتى أسوأ من ذلك.

هل يمكن التعامل مع هذا beatifully مع فئة DialogService التي تقع على عاتق المسؤولية لإظهار نافذة الحوار والنتيجة الحوار العودة. لدي إنشاء مشروع عينة يوضح أنه التطبيق والاستخدام.

هنا الأجزاء الأكثر أهمية:

//we will call this interface in our viewmodels
public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel, string caption);
}

//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
    public string Message {get; set;}
    public void ShowLoginCommandExecute()
    {
        var loginViewModel = new LoginViewModel();
        var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");

        //after dialog is closed, do someting
        if (dialogResult == true && loginViewModel.IsLoginSuccessful)
        {
            this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
        }
    }
}


public class DialogService : IDialogService
{
    public bool? ShowDialog(object dialogViewModel, string caption)
    {
        var contentView = ViewLocator.GetView(dialogViewModel);
        var dlg = new DialogWindow
        {
            Title = caption
        };
        dlg.PART_ContentControl.Content = contentView;

        return dlg.ShowDialog();
    }
}

أليس هذا أبسط؟ أكثر straitforward ، أكثر قابلية للقراءة وأخيرا وليس آخرا أسهل لتصحيح من EventAggregator أو حلول أخرى مماثلة؟

كما ترون ، في نماذجي الشخصية لقد استخدمت أسلوب ViewModel الأول الموضح في مشاركتي هنا: أفضل ممارسة لاستدعاء عرض من ViewModel في WPF

بالطبع ، في العالم الحقيقي ، يجب أن يكون لدى DialogService.ShowDialog خيار أكبر لتكوين مربع الحوار ، على سبيل المثال الأزرار والأوامر التي يجب تنفيذها. هناك طريقة مختلفة للقيام بذلك ، ولكن خارج نطاق :)


لماذا لا مجرد تمرير النافذة كمعلمة الأمر؟

C #:

 private void Cancel( Window window )
  {
     window.Close();
  }

  private ICommand _cancelCommand;
  public ICommand CancelCommand
  {
     get
     {
        return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
                                                      ( window ) => Cancel( window ),
                                                      ( window ) => ( true ) ) );
     }
  }

XAML:

<Window x:Class="WPFRunApp.MainWindow"
        x:Name="_runWindow"
...
   <Button Content="Cancel"
           Command="{Binding Path=CancelCommand}"
           CommandParameter="{Binding ElementName=_runWindow}" />

من وجهة نظري ، يكون السؤال جيدًا نظرًا لأن الطريقة نفسها لا تستخدم فقط في نافذة "تسجيل الدخول" ، ولكن لأي نوع من أنواعها. لقد مررت بالكثير من الاقتراحات ولا أحد على ما يرام بالنسبة لي. يرجى الاطلاع على نوعي ، التي أخذت من مقالة نمط تصميم MVVM .

يجب أن تكون كل فئة RequestClose موروثة من WorkspaceViewModel يحتوي على RequestClose envent و خاصية CloseCommand لنوع ICommand . سيؤدي تطبيق افتراضي لخاصية CloseCommand إلى رفع حدث RequestClose .

OnLoaded النافذة ، يجب إلغاء طريقة OnLoaded في نافذتك:

void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
    CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
    DataContext = customer;
    customer.RequestClose += () => { Close(); };
}

أو طريقة OnStartup من تطبيقك:

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        var viewModel = new MainWindowViewModel();
        viewModel.RequestClose += window.Close;
        window.DataContext = viewModel;

        window.Show();
    }

أعتقد أن حدث RequestClose وتطبيق خاصية CloseCommand في WorkspaceViewModel واضحان جدًا ، لكنني RequestClose :

public abstract class WorkspaceViewModel : ViewModelBase // There are nothing interest in ViewModelBase, it only implements INotifyPropertyChanged interface only
{
    RelayCommand _closeCommand;
    public ICommand CloseCommand
    {
        get
        {
            if (_closeCommand == null)
            {
                _closeCommand = new RelayCommand(
                   param => Close(),
                   param => CanClose()
                   );
            }
            return _closeCommand;
        }
    }

    public event Action RequestClose;

    public virtual void Close()
    {
        if ( RequestClose!=null )
        {
            RequestClose();
        }
    }

    public virtual bool CanClose()
    {
        return true;
    }
}

وشفرة المصدر من RelayCommand :

public class RelayCommand : ICommand
{
    #region Constructors

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields
}

ملاحظة: لا تعاملني بشكل سيء لتلك المصادر ، إذا كان لدي ذلك بالأمس من شأنه أن يوفر لي بضع ساعات ...

PPS أي تعليقات أو اقتراحات هي موضع ترحيب.


هذا حل بسيط ونظيف - يمكنك إضافة حدث إلى ViewModel وإرشاد النافذة إلى إغلاق نفسها عند إطلاق هذا الحدث.

لمزيد من التفاصيل راجع منشور مدونتي ، أغلق النافذة من ViewModel .


هناك الكثير من التعليقات التي تدور حول إيجابيات وسلبيات MVVM هنا. بالنسبة لي ، أتفق مع نير ؛ إنها مسألة استخدام النمط بشكل مناسب ولا تتلاءم MVVM دائمًا. يبدو أن الناس قد أصبحوا مستعدين للتضحية بكل المبادئ الأكثر أهمية لتصميم البرمجيات فقط للحصول عليها لتلائم MVVM.

ومع ذلك ، .. أعتقد أن قضيتك يمكن أن تكون مناسبة بشكل جيد مع القليل من إعادة البناء.

في معظم الحالات التي صادفتها ، تمكّنك WPF من الحصول عليها دون استخدام Window متعددة. ربما يمكنك محاولة استخدام s s و s Page بدلاً من Windows باستخدام DialogResult s.

في حالتك سيكون اقتراحا ليتم التعامل مع LoginCommand وإذا كان تسجيل الدخول غير صحيح ، قم بتعيين خاصية على LoginFormViewModel إلى قيمة مناسبة ( false أو بعض قيمة التعداد مثل UserAuthenticationStates.FailedAuthentication ). سوف تفعل الشيء نفسه لتسجيل الدخول بنجاح ( true أو بعض قيمة التعداد الأخرى). ستستخدم بعد ذلك DataTrigger الذي يستجيب لمختلف حالات استيقان المستخدم ويمكن أن يستخدم أداة Setter بسيطة لتغيير الخاصية Source Frame .

بعد أن تقوم نافذة تسجيل الدخول الخاصة بك بإرجاع DialogResult أعتقد أنه هو المكان الذي تشعر بالحيرة. أن DialogResult هو حقًا خاصية ViewModel. في بلدي ، تجربة محدودة من المسلم به مع WPF ، عندما لا يبدو أن هناك شيء صحيح ، لأنني أفكر فيما يتعلق بكيفية القيام بنفس الشيء في WinForms.

امل ان يساعد.


يمكن أن يكون ViewModel يعرض حدثًا يسجله العرض. ثم ، عندما يحدد ViewModel وقته لإغلاق العرض ، فإنه يطلق هذا الحدث الذي يؤدي إلى إغلاق العرض. إذا كنت تريد إرجاع قيمة محددة ، سيكون لديك خاصية في ViewModel لذلك.


Create a Dependency Property in your View /any UserControl (or Window you want to close). مثل أدناه:

 public bool CloseTrigger
        {
            get { return (bool)GetValue(CloseTriggerProperty); }
            set { SetValue(CloseTriggerProperty, value); }
        }

        public static readonly DependencyProperty CloseTriggerProperty =
            DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));

        private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
        {
            //write Window Exit Code
        }

And bind it from your ViewModel's property :

<Window x:Class="WpfTempProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Width="525"
        CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"

Property In VeiwModel :

private bool closeWindow;

    public bool CloseWindow
    {
        get { return closeWindow; }
        set 
        { 
            closeWindow = value;
            RaiseChane("CloseWindow");
        }
    }

Now trigger the close operation by changing the CloseWindow value in ViewModel. :)


Here is the simple bug free solution (with source code), It is working for me.

  1. Derive your ViewModel from INotifyPropertyChanged

  2. Create a observable property CloseDialog in ViewModel

    public void Execute()
    {
        // Do your task here
    
        // if task successful, assign true to CloseDialog
        CloseDialog = true;
    }
    
    private bool _closeDialog;
    public bool CloseDialog
    {
        get { return _closeDialog; }
        set { _closeDialog = value; OnPropertyChanged(); }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    private void OnPropertyChanged([CallerMemberName]string property = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    

    }

  3. Attach a Handler in View for this property change

        _loginDialogViewModel = new LoginDialogViewModel();
        loginPanel.DataContext = _loginDialogViewModel;
        _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
    
  4. Now you are almost done. In the event handler make DialogResult = true

    protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "CloseDialog")
        {
            DialogResult = true;
        }
    }
    

public partial class MyWindow: Window
{
    public ApplicationSelection()
    {
      InitializeComponent();

      MyViewModel viewModel = new MyViewModel();

      DataContext = viewModel;

      viewModel.RequestClose += () => { Close(); };

    }
}

public class MyViewModel
{

  //...Your code...

  public event Action RequestClose;

  public virtual void Close()
  {
    if (RequestClose != null)
    {
      RequestClose();
    }
  }

  public void SomeFunction()
  {
     //...Do something...
     Close();
  }
}




mvvm