J'ai récemment eu le problème de créer des dialogues d'ajout et d'édition pour mon application wpf.
Tout ce que je veux faire dans mon code était quelque chose comme ça. (J'utilise principalement viewmodel d'abord avec mvvm)
ViewModel qui appelle une fenêtre de dialogue:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
// Do anything with the dialog result
Comment ça marche?
Tout d'abord, j'ai créé un service de dialogue:
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
est une fenêtre spéciale mais simple. J'en ai besoin pour contenir mon contenu:
<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>
Un problème avec les dialogues dans wpf est le dialogresult = true
ne peut être réalisé qu'en code. C'est pourquoi j'ai créé une interface pour que mon dialogviewmodel
l'implémente.
public class RequestCloseDialogEventArgs : EventArgs
{
public bool DialogResult { get; set; }
public RequestCloseDialogEventArgs(bool dialogresult)
{
this.DialogResult = dialogresult;
}
}
public interface IDialogResultVMHelper
{
event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog;
}
Chaque fois que mon ViewModel pense qu'il est temps pour dialogresult = true
, puis relance cet événement.
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;
}
}
Maintenant, au moins, je dois créer un DataTemplate
dans mon fichier de ressources (app.xaml
ou quelque chose):
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>
Bien, je peux maintenant appeler des dialogues à partir de mes modèles de vue:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
Maintenant ma question, voyez-vous des problèmes avec cette solution?
Edit: pour être complet. Le ViewModel doit implémenter IDialogResultVMHelper
pour ensuite le relever dans un OkCommand
ou quelque chose comme ceci:
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: J'ai utilisé le code d'ici pour affaiblir mon registre EventHandler:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Le site Web n'existe plus, 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;
}
}
C'est une bonne approche et j'ai utilisé des approches similaires dans le passé. Fonce!
Une chose mineure que je ferais assurément serait que l'événement reçoive un booléen lorsque vous devez définir "false" dans DialogResult.
event EventHandler<RequestCloseEventArgs> RequestCloseDialog;
et la classe EventArgs:
public class RequestCloseEventArgs : EventArgs
{
public RequestCloseEventArgs(bool dialogResult)
{
this.DialogResult = dialogResult;
}
public bool DialogResult { get; private set; }
}
J'utilise une approche presque identique depuis plusieurs mois maintenant et j'en suis très heureux (c'est-à-dire que je n'ai pas encore ressenti le besoin de la réécrire complètement ...)
Dans mon implémentation, j’utilise IDialogViewModel
qui expose des éléments tels que le titre, les boutons à afficher (pour conserver une apparence cohérente dans tous les dialogues), un événement RequestClose
et un peu d'autres choses pour pouvoir contrôler la taille et le comportement de la fenêtre
Si vous parlez de fenêtres de dialogue et pas seulement de boîtes de message contextuelles, veuillez considérer mon approche ci-dessous. Les points clés sont:
Module Controller
Dans le constructeur de chaque ViewModel
(vous pouvez utiliser l'injection).Module Controller
A des méthodes publiques/internes pour créer des fenêtres de dialogue (juste pour créer, sans renvoyer de résultat). Par conséquent, pour ouvrir une fenêtre de dialogue dans ViewModel
j’écris: controller.OpenDialogEntity(bla, bla...)
Avantages:
Module Controller
Est un moyen simple d’éviter les fortes références et permet toujours d’utiliser des maquettes pour les tests.Inconvénients:
<T>
où T
représente une énumération d'entités (ou, pour simplifier, ce peut être le type de ViewModel).Module Controller
Peut être submergé par les méthodes de création de fenêtres. Dans ce cas, il est préférable de le scinder en plusieurs modules.P.S. J'utilise cette approche depuis assez longtemps et je suis prête à défendre son éligibilité dans des commentaires et à donner quelques exemples si nécessaire.