|
Why not raise an event from the model, then subscribe to the event for each item in the list?
If it's not broken, fix it until it is
|
|
|
|
|
The model already has the required event - the propertychanged event
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
modified 13-Jan-14 7:03am.
|
|
|
|
|
Hello,
Please can someone convert me the following xaml code to C# code:
<DataGridTextColumn Header="Ordinal" IsReadOnly="True">
<DataGridTextColumn.Binding>
<MultiBinding
Converter="{StaticResource rowNumberConverter}">
<Binding />
<Binding
RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}" />
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
|
|
|
|
|
I think the best thing to do is to convert the xaml headers, like DataGridTextColumn to equivalent classes in C#. The red items in xaml are the properties for that class. For example, the Header and IsReadOnly is most likely in the original DataGridTextColumn class as additional properties. Most xaml headers can be located as similar class names. Finally, the binding could be changed to reference the DataGrid's name instead.
Ben Scharbach
Temporalwars.Com
YouTube:Ben Scharbach
|
|
|
|
|
I am creating a DialogService. I have looked at a bunch and don't like them for various reasons...
Assumptions
a) The serivce will maintain a registry of Views to ViewModels
b) The Views will always be Windows
c) The ViewModels will always inherit from _DialogBaseViewModel.
So here's what I have so far:
Interface
public interface IDialogService<TDialogType, TViewModelType>
where TDialogType : Window
where TViewModelType : _DialogBaseViewModel
{
void RegisterView(TDialogType dialog, TViewModelType viewModel);
bool? ShowDialog(TViewModelType viewModel);
}
Implementation
public class DialogService<TDialogType, TViewModelType> : IDialogService<TDialogType, TViewModelType>
where TDialogType : Window
where TViewModelType : _DialogBaseViewModel
{
private Dictionary<TDialogType, TViewModelType> registry = new Dictionary<TDialogType, TViewModelType>();
public void RegisterView(TDialogType dialogType, TViewModelType viewModelType)
{
bool exists = registry.Where(x => x.Key.GetType() == dialogType.GetType()).Any();
if (!exists)
{
registry.Add(dialogType, viewModelType);
}
}
public bool? ShowDialog(TViewModelType viewModel)
{
TDialogType dialog = registry.Where(x => x.Value == viewModel).Select(x => x.Key).FirstOrDefault();
dialog.DataContext = viewModel;
dialog.ShowDialog();
bool? result = dialog.DialogResult;
return result;
}
}
Usage
public class TestIt
{
public void Test()
{
DialogService<Window, _DialogBaseViewModel> service = new DialogService<Window, _DialogBaseViewModel>();
service.RegisterView(new PersonView(), new PersonViewModel());
service.RegisterView(new InvoiceView(), new InvoiceViewModel());
service.RegisterView(new AnotherView(), new AnotherViewModel());
bool? dialogResult = service.ShowDialog(new InvoiceViewModel());
}
}
Problem
How do I change this so that I register types instead of concrete classes? If you look at the test you can see that I had to create instances to get it to work... This isn't right.
Thanks
If it's not broken, fix it until it is
|
|
|
|
|
a rough example.
Interface
public interface IDialogService<TDialogType, TViewModelType>
where TDialogType : Window
where TViewModelType : DialogBaseViewModel
{
void RegisterView(Type dialogType, Type viewModelType);
bool? ShowDialog(TViewModelType viewModel);
}
Implementation
public class DialogService<TDialogType, TViewModelType> : IDialogService<TDialogType, TViewModelType>
where TDialogType : Window
where TViewModelType : DialogBaseViewModel
{
private Dictionary<Type, Type> registry = new Dictionary<Type, Type>();
public void RegisterView(Type dialogType, Type viewModelType)
{
if (!dialogType.IsSubclassOf(typeof(TDialogType)))
{
throw new ArgumentException(string.Format("The dialogType must be a subclass of type {0}.", typeof(TDialogType).Name), "dialogType");
}
if (!viewModelType.IsSubclassOf(typeof(TViewModelType)))
{
throw new ArgumentException(string.Format("The viewModelType must be a subclass of type {0}.", typeof(TViewModelType).Name), "viewModelType");
}
bool exists = registry.Where(x => x.Key == dialogType).Any();
if (!exists)
{
registry.Add(dialogType, viewModelType);
}
}
public bool? ShowDialog(TViewModelType viewModel)
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
var dialog = (TDialogType)TypeDescriptor.CreateInstance(null, dialogType, null, null);
dialog.DataContext = viewModel;
dialog.ShowDialog();
bool? result = dialog.DialogResult;
return result;
}
}
Usage
DialogService<Window, DialogBaseViewModel> dialogService = new DialogService<Window, DialogBaseViewModel>();
dialogService.RegisterView(typeof(PersonView), typeof(PersonViewModel));
dialogService.RegisterView(typeof(InvoiceView), typeof(InvoiceViewModel));
dialogService.RegisterView(typeof(AnotherView), typeof(AnotherViewModel));
dialogService.ShowDialog(new PersonViewModel());
|
|
|
|
|
Thank you Sir!
If it's not broken, fix it until it is
|
|
|
|
|
Although answered in the above post, I will say this:
To me, the word "service" screams static class. What use is a service if you have to create it and register all your views every time you are in a different scope? I would create the service class as a static item and register views on it, that way the registered views are available to the entire application without having to recreate the registry.
You would have to use some kind of initialization logic to set up the service (register all the available views) that should be called during the startup process of your program.
|
|
|
|
|
I agree. Just wanted to get it running first. I'll convert it to a static class
If it's not broken, fix it until it is
|
|
|
|
|
So, making it static, I now have:
DialogService<Window, _DialogBaseViewModel>.RegisterView(typeof(SQLConnectionView), typeof(SQLConnectionViewModel));
bool? dialogResult = DialogService<Window, _DialogBaseViewModel>.ShowDialog(new SQLConnectionViewModel());
I'm not sure I like having to specify the types each time I call ShowDialog
If it's not broken, fix it until it is
|
|
|
|
|
Ok, here's how I would change it:
Implementation:
public static class DialogService
{
private static Dictionary<Type, Type> registry = new Dictionary<Type, Type>();
public static void RegisterView(Type dialogType, Type viewModelType)
{
}
public static bool? ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : DialogBaseViewModel
{
}
}
You don't seem to be using the TDialogType anywhere (you could replace it with "Window" in the RegisterView and ShowDialog methods and get exactly the same effect). There is no reason that I see that you need type parameters on the class itself where the type parameters on the method work fine (you only need them if you were using your dictionary as TDialogType,TViewModelType, which you aren't.
|
|
|
|
|
LOL, I just did that very change!
Thanks
If it's not broken, fix it until it is
|
|
|
|
|
Ok, so here's what I have now. Then a question..
Service
public static class DialogService
{
private static Dictionary<Type, Type> registry = new Dictionary<Type, Type>();
public static void RegisterDialog(Type dialogType, Type viewModelType)
{
if (!dialogType.IsSubclassOf(typeof(Window)))
{
throw new ArgumentException("The dialogType must be a subclass of type Window.");
}
bool exists = registry.Where(x => x.Key == dialogType).Any();
if (!exists)
{
registry.Add(dialogType, viewModelType);
}
}
public static bool? ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
dialog.ShowDialog();
bool? result = dialog.DialogResult;
return result;
}
}
Usage
DialogService.RegisterDialog(typeof(PersonView), typeof(PersonViewModel));
bool? dialogResult = DialogService.ShowDialog(new PersonViewModel());
The reason I'm forcing the VM to inherit from _DialogBaseViewModel is that _DialogBaseViewModel has a property called DialogResult. I have a DP that is set in a window's header XAML that sets the DialogResult from the VM and closes the window. Works real nice:
<Window x:Class="Osprey.UI.WPF.Views.MyDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:behaviors="clr-namespace:Osprey.UI.WPF.Classes.Behaviors"
Title="My Dialog"
xmlns:xc="clr-namespace:Osprey.UI.WPF.Classes"
xc:DialogCloser.DialogResult="{Binding DialogResult}">
Problem
How would you return an object from this service?
Assume a dialog prompts the user to enter some data. That data could be stored to a model. I would want the service to return the model if DialogResult was true.
My first thought was to have a static wrapper class around this service that has methods for each type I would want to return. If null is returned then the dialog's DialogResult was false.
What do you think?
If it's not broken, fix it until it is
|
|
|
|
|
I would add another class, something like:
public class DialogResultEx
{
public bool? DialogResult { get; set; }
public object DialogData { get; set; }
}
Then in your ShowDialog method:
public static DialogResultEx ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
dialog.ShowDialog();
DialogResultEx dlgResult = new DialogResult();
dlgResult.DialogResult = dialog.DialogResult;
dlgResult.DialogData = viewModel.WhateverData;
return dlgResult;
}
You could even make it implicitly castable to a bool...
public class DialogResultEx
{
public bool? DialogResult { get; set; }
public object DialogData { get; set; }
public static implicit operator bool?(DialogResultEx dlgResult)
{
return dlgResult.DialogResult;
}
}
So you wouldn't have to change much of your upstream code that didn't need the entire class.
|
|
|
|
|
I like it. One question....
In the ShowDialog you have:
DialogResultEx dlgResult = new DialogResultEx();
dlgResult.DialogResult = dialog.DialogResult;
dlgResult.DialogData = viewModel.WhateverData;
How does this method know what object/value on the VM to get? How does 'viewModel.WhateverData' get set from with in this method?
If it's not broken, fix it until it is
|
|
|
|
|
I figured that your model derives from a base type, you can have an object in the model to store any return value (or you could just store the entire view model if you want).
I don't know where you would store the return value in the view model, so I put a placeholder there. You didn't really specify where this would be stored, but I assume you have a field in the model that you can store return data in.
I would put a virtual field in the viewmodel base class (returns null by default) that the derived classes can override to return whatever.
For example:
public abstract class _DialogBaseViewModel
{
public virtual object DialogData { get { return null; } }
}
Then a derived class could write:
public class InputDialogViewModel : _DialogBaseViewModel
{
private string _textData = string.Empty;
public override object DialogData
{
get
{
return _textData;
}
}
}
|
|
|
|
|
OK, I see.. I like the design.
I just coded this:
Service
Same as before....
public static class DialogService
{
private static Dictionary<Type, Type> registry = new Dictionary<Type, Type>();
public static void RegisterDialog(Type dialogType, Type viewModelType)
{
if (!dialogType.IsSubclassOf(typeof(Window)))
{
throw new ArgumentException("The dialogType must be a subclass of type Window.");
}
bool exists = registry.Where(x => x.Key == dialogType).Any();
if (!exists)
{
registry.Add(dialogType, viewModelType);
}
}
public static bool? ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
dialog.ShowDialog();
return dialog.DialogResult.Value;
}
}
DialogManager
A wrapper class to the Sertvice:
public static class DialogManager
{
static DialogManager()
{
DialogService.RegisterDialog(typeof(SQLConnectionView), typeof(SQLConnectionViewModel));
}
public static string OpenSQLConnectionDialog()
{
string result = string.Empty;
SQLConnectionViewModel vm = new SQLConnectionViewModel();
bool? dialogResult = DialogService.ShowDialog(vm);
if (dialogResult.Value)
{
result = vm.ConnectionString;
}
return result;
}
}
Usage
var connString = DialogManager.OpenSQLConnectionDialog();
By doing it with the wrapper, I can return a value, or the whole VM if I want.
Drawback is that I'd have to specified methods for each dialog I want to open, and possibly more if I wanted different return values;
If it's not broken, fix it until it is
|
|
|
|
|
Ok, I set it up like you suggested. Very nice, and no need for a wrapper class.
Thank you Sir!
If it's not broken, fix it until it is
|
|
|
|
|
Simplicity is a beautiful thing 
|
|
|
|
|
 Yes it is.
One thing I forgot was the Owner window. So I added an overload.
public static DialogResultEx ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
if (dialogType == null)
{
throw new ArgumentException(string.Format("There is no registered dialog corresponding to a view model of type {0}.", viewModel));
}
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
dialog.ShowDialog();
DialogResultEx dlgResult = new DialogResultEx();
dlgResult.DialogResult = dialog.DialogResult;
dlgResult.DialogData = viewModel.DialogData;
return dlgResult;
}
public static DialogResultEx ShowDialog<TViewModelType, TOwnerType>(TViewModelType viewModel, Type ownerType) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
if (dialogType == null)
{
throw new ArgumentException(string.Format("There is no registered dialog corresponding to a view model of type {0}.", viewModel));
}
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
if (ownerType != null)
{
Window ownerWindow = FindWindow(ownerType);
dialog.Owner = ownerWindow;
}
dialog.ShowDialog();
DialogResultEx dlgResult = new DialogResultEx();
dlgResult.DialogResult = dialog.DialogResult;
dlgResult.DialogData = viewModel.DialogData;
return dlgResult;
}
Usage:
DialogResultEx dialogResult1 = DialogService.ShowDialog<SQLConnectionViewModel>(new SQLConnectionViewModel());
DialogResultEx dialogResult2 = DialogService.ShowDialog<SQLConnectionViewModel, MainWindowView>(new SQLConnectionViewModel(), typeof(MainWindowView));
Given the generics, how do I combine this into one method?
If it's not broken, fix it until it is
|
|
|
|
|
I would overload it...
public static DialogResultEx ShowDialog<TViewModelType>(TViewModelType viewModel, Type ownerType) where TViewModelType : _DialogBaseViewModel
{
Type dialogType = registry.Where(x => x.Value == viewModel.GetType()).Select(x => x.Key).FirstOrDefault();
if (dialogType == null)
{
throw new ArgumentException(string.Format("There is no registered dialog corresponding to a view model of type {0}.", viewModel));
}
var dialog = (Window)Activator.CreateInstance(dialogType);
dialog.DataContext = viewModel;
if (ownerType != null)
{
Window ownerWindow = FindWindow(ownerType);
dialog.Owner = ownerWindow;
}
dialog.ShowDialog();
DialogResultEx dlgResult = new DialogResultEx();
dlgResult.DialogResult = dialog.DialogResult;
dlgResult.DialogData = viewModel.DialogData;
return dlgResult;
}
public static DialogResultEx ShowDialog<TViewModelType>(TViewModelType viewModel) where TViewModelType : _DialogBaseViewModel
{
return ShowDialog<TViewModelType>(viewModel, null);
}
Again, watch those generics and how you use them. You declare the generic TOwnerType but you don't ever use it, so there is no need for it. Using the above you can have both for when you need the owner window and for when you don't, and you have all the code re-use so you only have to maintain in one place.
|
|
|
|
|
OK, I see.. I was trying to set the Owner type in the generic list. Couldn't make it work.
Many thanks!
If it's not broken, fix it until it is
|
|
|
|
|
Thanks for all your help. I appreciate it. It's working great. I added a ShowMessageBox method.
So now that I have this done, I was thinking of writing an article. I know it's been done before, but what do you think?
If it's not broken, fix it until it is
|
|
|
|
|
|
Kevin Marois wrote: I was thinking of writing an article
Excellent - it would save me at least 2 bookmarks and chasing through the thread. I have been looking for a simple dialog service and this seems to meet the requirements without delving too deep (I LIKE simple).
Never underestimate the power of human stupidity
RAH
|
|
|
|
|