Dobra czy zła praktyka dla dialogów w wpf z MVVM?
Ostatnio miałem problem z tworzeniem okien dialogowych Dodaj i edytuj dla mojej aplikacji wpf.
Wszystko, co chcę zrobić w moim kodzie, to coś takiego. (Używam głównie ViewModel first approach z mvvm)
ViewModel wywołujący okno dialogowe:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result
Jak to działa?
Najpierw utworzyłem usługę dialogową:
public interface IUIWindowDialogService
{
bool? ShowDialog(string title, object datacontext);
}
public class WpfUIWindowDialogService : IUIWindowDialogService
{
public bool? ShowDialog(string title, object datacontext)
{
var win = new WindowDialog();
win.Title = title;
win.DataContext = datacontext;
return win.ShowDialog();
}
}
WindowDialog
jest specjalnym, ale prostym oknem. Potrzebuję go do przechowywania mojej treści:
<Window x:Class="WindowDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WindowDialog"
WindowStyle="SingleBorderWindow"
WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight">
<ContentPresenter x:Name="DialogPresenter" Content="{Binding .}">
</ContentPresenter>
</Window>
Problemem z oknami dialogowymi w wpf jest dialogresult = true
can tylko w kodzie. Dlatego stworzyłem interfejs dla mojej dialogviewmodel
, aby go zaimplementować.
public class RequestCloseDialogEventArgs : EventArgs
{
public bool DialogResult { get; set; }
public RequestCloseDialogEventArgs(bool dialogresult)
{
this.DialogResult = dialogresult;
}
}
public interface IDialogResultVMHelper
{
event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
}
Kiedy mój ViewModel myśli, że nadszedł czas na dialogresult = true
, a następnie podnieść to wydarzenie.
public partial class DialogWindow : Window
{
// Note: If the window is closed, it has no DialogResult
private bool _isClosed = false;
public DialogWindow()
{
InitializeComponent();
this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged;
this.Closed += DialogWindowClosed;
}
void DialogWindowClosed(object sender, EventArgs e)
{
this._isClosed = true;
}
private void DialogPresenterDataContextChanged(object sender,
DependencyPropertyChangedEventArgs e)
{
var d = e.NewValue as IDialogResultVMHelper;
if (d == null)
return;
d.RequestCloseDialog += new EventHandler<RequestCloseDialogEventArgs>
(DialogResultTrueEvent).MakeWeak(
eh => d.RequestCloseDialog -= eh;);
}
private void DialogResultTrueEvent(object sender,
RequestCloseDialogEventArgs eventargs)
{
// Important: Do not set DialogResult for a closed window
// GC clears windows anyways and with MakeWeak it
// closes out with IDialogResultVMHelper
if(_isClosed) return;
this.DialogResult = eventargs.DialogResult;
}
}
Teraz przynajmniej muszę utworzyć DataTemplate
w moim pliku zasobów (app.xaml
czy coś):
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>
Cóż to wszystko, mogę teraz wywoływać okna dialogowe z moich viewmodels:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
Teraz moje pytanie, Czy widzisz jakieś problemy z tym rozwiązaniem?
Edit: dla kompletności. The ViewModel powinien zaimplementować IDialogResultVMHelper
i wtedy może go podnieść w OkCommand
lub coś w tym stylu:
public class MyViewmodel : IDialogResultVMHelper
{
private readonly Lazy<DelegateCommand> _okCommand;
public MyViewmodel()
{
this._okCommand = new Lazy<DelegateCommand>(() =>
new DelegateCommand(() =>
InvokeRequestCloseDialog(
new RequestCloseDialogEventArgs(true)), () =>
YourConditionsGoesHere = true));
}
public ICommand OkCommand
{
get { return this._okCommand.Value; }
}
public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e)
{
var handler = RequestCloseDialog;
if (handler != null)
handler(this, e);
}
}
EDIT 2: użyłem kodu stąd, aby zarejestrować mój EventHandler weak:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Strona już nie istnieje, WebArchive Mirror )
public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler)
where TE : EventArgs;
public interface IWeakEventHandler<TE>
where TE : EventArgs
{
EventHandler<TE> Handler { get; }
}
public class WeakEventHandler<T, TE> : IWeakEventHandler<TE>
where T : class
where TE : EventArgs
{
private delegate void OpenEventHandler(T @this, object sender, TE e);
private readonly WeakReference mTargetRef;
private readonly OpenEventHandler mOpenHandler;
private readonly EventHandler<TE> mHandler;
private UnregisterCallback<TE> mUnregister;
public WeakEventHandler(EventHandler<TE> eventHandler,
UnregisterCallback<TE> unregister)
{
mTargetRef = new WeakReference(eventHandler.Target);
mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate(
typeof(OpenEventHandler),null, eventHandler.Method);
mHandler = Invoke;
mUnregister = unregister;
}
public void Invoke(object sender, TE e)
{
T target = (T)mTargetRef.Target;
if (target != null)
mOpenHandler.Invoke(target, sender, e);
else if (mUnregister != null)
{
mUnregister(mHandler);
mUnregister = null;
}
}
public EventHandler<TE> Handler
{
get { return mHandler; }
}
public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh)
{
return weh.mHandler;
}
}
public static class EventHandlerUtils
{
public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler,
UnregisterCallback<TE> unregister)
where TE : EventArgs
{
if (eventHandler == null)
throw new ArgumentNullException("eventHandler");
if (eventHandler.Method.IsStatic || eventHandler.Target == null)
throw new ArgumentException("Only instance methods are supported.",
"eventHandler");
var wehType = typeof(WeakEventHandler<,>).MakeGenericType(
eventHandler.Method.DeclaringType, typeof(TE));
var wehConstructor = wehType.GetConstructor(new Type[]
{
typeof(EventHandler<TE>), typeof(UnregisterCallback<TE>)
});
IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
new object[] { eventHandler, unregister });
return weh.Handler;
}
}
3 answers
Jest to dobre podejście i używałem podobnych w przeszłości. Dawaj!
Jedną z drobnych rzeczy, które zdecydowanie bym zrobił, to sprawić, by Zdarzenie otrzymało wartość logiczną, gdy trzeba ustawić "false" W DialogResult.
event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
I klasa EventArgs:
public class RequestCloseEventArgs : EventArgs
{
public RequestCloseEventArgs(bool dialogResult)
{
this.DialogResult = dialogResult;
}
public bool DialogResult { get; private set; }
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-10 17:57:19
Od kilku miesięcy stosuję niemal identyczne podejście i jestem z niego bardzo zadowolony (tzn. nie czułem jeszcze ochoty, aby go całkowicie przepisać...)
W mojej implementacji używam IDialogViewModel
, który eksponuje rzeczy takie jak tytuł, standardowe przyciski do wyświetlania (aby mieć spójny wygląd we wszystkich oknach dialogowych), Zdarzenie RequestClose
i kilka innych rzeczy, aby móc kontrolować rozmiar i zachowanie okna
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-09 22:56:18
Jeśli mówisz o oknach dialogowych, a nie tylko o wyskakujących okienkach z wiadomościami, rozważ moje podejście poniżej. Najważniejsze punkty to:
- przekazuję odniesienie do
Module Controller
do konstruktora każdegoViewModel
(można użyć injection). -
Module Controller
posiada publiczne / wewnętrzne metody tworzenia okien dialogowych (tylko tworzenie, bez zwracania wyniku). Dlatego aby otworzyć okno dialogowe wViewModel
piszę:controller.OpenDialogEntity(bla, bla...)
- każde okno dialogowe powiadamia o swoim wyniku (jak OK, Zapisz, Anuluj , itd.) poprzez słabe zdarzenia . Jeśli używasz PRISM, łatwiej jest publikować powiadomienia za pomocą This EventAggregator.
- aby obsłużyć wyniki dialogu, używam subskrypcji powiadomień (ponownie słabe zdarzenia i EventAggregator W przypadku PRISM). Aby zmniejszyć zależność od takich powiadomień, użyj niezależnych klas ze standardem powiadomienia.
Plusy:
- mniej kodu. Nie mam nic przeciwko używaniu interfejsów, ale widziałem zbyt wiele projektów, w których nadmierność używania interfejsów i warstw abstrakcji powoduje więcej kłopotów niż pomocy.
- otwieranie okien dialogowych przez
Module Controller
to prosty sposób na uniknięcie silnych odniesień i nadal pozwala na wykorzystanie makiet do testów. - powiadomienia poprzez słabe zdarzenia zmniejszają ilość potencjalnej pamięci przecieki.
Wady:
- nie jest łatwo odróżnić wymagane powiadomienie od innych w obsłudze. Dwa rozwiązania:
- Po otwarciu okna dialogowego wyślij unikalny token i sprawdź ten token w subskrypcji.]}
- użyj ogólnych klas powiadomień
<T>
GdzieT
jest wyliczaniem encji (lub dla uproszczenia może to być Typ ViewModel).
- użyj ogólnych klas powiadomień
- dla projektu powinna być Umowa o wykorzystaniu klas powiadomień do zapobiec ich powielaniu.
- dla ogromnie dużych projektów
Module Controller
mogą być przytłoczone metodami tworzenia okien. W tym przypadku lepiej podzielić go na kilka modułów.
P. S. używam tego podejścia od dość dawna i jestem gotowy bronić jego kwalifikowalności w komentarzach i podać kilka przykładów, jeśli zajdzie taka potrzeba.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-03 08:05:08