Bonne ou mauvaise pratique pour les Dialogues en wpf avec MVVM?
J'ai eu dernièrement le problème de la création d'ajouter et de modifier des boîtes de dialogue pour mon application wpf.
Tout ce que je veux faire dans mon code était quelque chose comme cela. (J'utilise surtout viewmodel première approche 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 fonctionne?
Tout d'abord, j'ai créé une boîte de dialogue service:
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 simple fenêtre. J'en ai besoin pour tenir 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 en wpf est le dialogresult = true
ne peut être atteint que dans le code. C'est pourquoi j'ai créé une interface pour mon dialogviewmodel
à mettre en œuvre.
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 déclencher 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, j'ai créer un DataTemplate
dans mon fichier de ressources(app.xaml
ou quelque chose):
<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" >
<DialogView:EditOrNewAuswahlItem/>
</DataTemplate>
Bien c'est tout, je peux maintenant appeler les boîtes de dialogue à partir de mon viewmodel:
var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM);
Maintenant, ma question, voyez-vous des problèmes avec cette solution?
Edit: pour être complet. Ce Dernier doit mettre en œuvre IDialogResultVMHelper
et puis elle peut 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 à partir d'ici pour faire mon Gestionnaire enregistrer de faibles:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Site web n'existe plus, WebArchive Miroir)
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;
}
}
- vous êtes probablement manquant la xmlns:x="schemas.microsoft.com/winfx/2006/xaml" refernece dans votre WindowDialog XAML.
- En fait l'espace de noms est xmlns:x="[http://]schemas.microsoft.com/winfx/2006/xaml" sans les crochets
- voir stackoverflow.com/questions/16993433/...
- Salut @blindmeis. Je sais que près de 5 ans ont passé, mais je suis bloqué à la fermeture de la boîte de dialogue de la partie. Pouvez-vous nous expliquer en partie?
- votre dernier doit mettre en œuvre IDialogResultVMHelper et de soulever le cas de cours. voir mon edit
- je vous remercie pour votre réponse. J'ai déjà mis en place cette interface. Mon problème est que j'ai aussi placé le contexte de données dans le code xaml de mon contrôle de l'utilisateur. en tant que <UserControl.DataContext> </UserControl.DataContext>. À cause de cela, à la InitializeComponenet() de la partie nouvelle instance de cette classe a été créée pour l'événement a été supprimé. Après une journée coincé j'ai supprimé les données de réglage contexte de xaml et il fonctionne comme un charme. Merci pour ce superbe post. Il est très utile.
- j'ai toujours utiliser viewmodel première approche avec les DataTemplates dans mes applications, sauf dans la mainwindow. donc je n'ai pas besoin de UserControl.DataContext dans une de mon point de vue 🙂
- Salut! Retardataire ici. Je ne suis pas la compréhension de la manière dont le Service a une référence à la WindowDialog. Quelle est la hiérarchie de vos modèles? Dans mon esprit, la Vue contient une référence à l'Viewmodel de l'assemblée et du Viewmodel pour le Service et le Modèle des assemblées. Ainsi, la couche de Service aurait aucune connaissance de la WindowDialog vue. Ce qui me manque?
- Cela pourrait être mieux posté dans codereview.stackexchange.com
- Salut @blindmeis, juste essayer d'envelopper ma tête autour de ce concept, je ne crois pas qu'il y a une certaine ligne exemple de projet que je puisse reprendre le dessus? Il y a un certain nombre de choses, je suis confus.
Vous devez vous connecter pour publier un commentaire.
C'est une bonne approche et j'ai utilisé semblables dans le passé. Foncez!!!!
Une petite chose que je serais certainement faire est de faire de l'événement une valeur booléenne pour quand vous avez besoin de mettre "false" dans le DialogResult.
et la classe EventArgs:
bool
il doit y avoir un personnalisé EventArgs dérivé de la base deEventArgs
classe qui contient unebool
de la propriété. LeEventHandler
délégué a une contrainte de classe sur le paramètre générique qui exige que le type qui découlent deEventArgs
. Avecbool
comme paramètre générique ce ne compile pas (du moins pas dans VS2010, je ne sais pas si cela n'a peut-être changer à partir de versions antérieures).J'ai été en utilisant à peu près la même approche pour plusieurs mois maintenant, et je suis très heureux avec elle (c'est à dire que je n'ai pas encore ressenti l'envie de le réécrire complètement...)
Dans mon application, j'utilise une
IDialogViewModel
qui expose des choses telles que le titre, le standad les boutons à afficher (dans l'ordre d'avoir une apparence cohérente à travers tous les dialogues), unRequestClose
événement, et quelques autres choses pour être en mesure de contrôler la taille de la fenêtre et le comportementSi vous parlez des fenêtres de dialogue et pas seulement à propos de la pop-up boîtes de message, merci de considérer ma démarche ci-dessous. Les points clés sont les suivants:
Module Controller
dans le constructeur de chaqueViewModel
(vous pouvez utiliser l'injection).Module Controller
a public/méthodes internes pour la création de fenêtres de dialogue (juste de la création, sans avoir à retourner un résultat). Donc pour ouvrir une fenêtre de dialogue dansViewModel
j'écris:controller.OpenDialogEntity(bla, bla...)
Pour:
Module Controller
est un moyen simple pour éviter de solides références et permet encore à l'utilisation de maquettes pour les tests.Contre:
<T>
oùT
est énumération des entités (ou pour des raisons de simplicité, il peut être un type d'ViewModel).Module Controller
pouvez être submergé par des méthodes pour la création de windows. Dans ce cas, il est préférable de les diviser en plusieurs modules.P. S. j'ai été en utilisant cette approche pour un temps assez long maintenant et prêt à défendre son admissibilité à des commentaires et donner des exemples si nécessaire.