Opening a new dialog using WPF with MVVM - wpf

I am currently using MVVM (Light) to build an application with WPF. However, in a few cases I must open a new dialog (also WPF) when the user clicks a button. However, this is being a tough fight.
Here is how I am doing it:
private void _ShowItemDialog(Item item)
{
var itemVM = new ItemViewModel();
itemVM.CurrentItem = item ?? new Item();
itemVM.Load();
var itemView = new View.ItemView() { DataContext = itemVM };
if (itemView.ShowDialog() == true)
{
if (item == null)
{
itemList.Add(itemVM.CurrentItem);
}
}
itemVM.Cleanup();
}
And the itemView XAML there is no binding to the DataContext, otherwise two different instances of the ViewModel would be created.
Inside the Window tag. To have the result at ShowDialog, I use the DialogCloser code:
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
In the ItemView, this is declared inside Window tag as follows:
my:DialogCloser.DialogResult="{Binding DialogResult}"
And when the dialog is closed, the closing event sets DialogResult to true or false.
This works perfectly for the first time the screen is opened, but it is not possible to open the dialog again after it is closed.
I would like to know if you have any better ideas for opening the dialog, and why this code does not work.
Thanks!
EDIT:
I have already fixed the code. What I need to do is create a new ViewModel and attach it to the DataContext every time the dialog is opened. Moreover, I had to remove the DataContext binding from XAML. Please check the code changes above.
With these changes I have found out that it is not possible to use the ViewModel from ViewModelLocator because it is a "singleton" and not a new instance at each new window. Therefore, the DialogResult held the last value and if I tried to change its value back to null (as it is when the ViewModel is initialized) an exception is thrown. Do you have any clues of why this happens? It would be very good for me to use the ViewModel from ViewModelLocator, since it would keep the same strategy throughout the system.
Thank you!

I do that by implementing static XxxxInteraction classes that have methods called for example NewUser(); That methods opens the Dialogs and do some work. In my ViewModel I call the XxxxInteraction classes via commands.
The efforts of that way of implementing is, that you can easely modify the methods in the static Interaction classes for using UnitTests.
public static class UserInteractions
{
public static User NewUser()
{
var userDialog = new NewUserDialog();
If(userDialog.ShowDialog() != true) return null;
var user = new User();
user.Name = userDialog.Name;
user.Age = userDialog.Age;
return user;
}
}
public class MyViewModel
{
...
public void NewUserCommandExecute()
{
var newUser = UserInteractions.NewUser();
if(newUser == null) return;
//Do some with new created user
}
}
NewUserDialog is a normal Window that is bound to a ViewModel too.
I think this is a good way of implementing dialogs for the mvvm pattern.

i've done this a while ago, i use a dialog service and call this service in my viewmodel. take a look.
EDIT: btw, thats all you have to do in your viewmodel
var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);

Related

Triggering an action of a UIElement from a ViewModel

I've implemented Wiesław Šoltés' awesome ZoomBorder, but I'm struggling a bit with WPF and MVVM. For the questions' completeness sake, ZoomBorder is a UIElement that inherits from Border and gives the user the possibility of zooming and panning the content of the inherited border. It also has the ability to reset the zoom and panning.
I'd like to make ZoomBorder react to an event being published by certain view model, so that when that event is published, ZoomBorder resets the zoom. In my implementation, the ZoomBorder's DataContext is a ContentViewModel, which has an IEventAggregator (Prism.Events) injected via Autofac. Ideally, I would have liked to inject the event aggregator directly into ZoomBorder, so that it can subscribe to the event, but I can't because the constructor needs to be parameterless.
So ContentViewModel has to subscribe to the event, but how would I invoke ZoomBorder's Reset method from ContentViewModel? I understand I'd be transgressing MVVM, but I don't know how else to do it. I thought about making ZoomBorder expose a Command via a dependency property, but then the Reset code would have to be on the view model, which it can't.
You can use the ServiceLocator inside of views or controls to resolve types from the container.
public ZoomBorder()
{
_eventAggregator = ServiceLocator.Current.GetInstance<IEventAggregator>();
}
If you use AutoFac and the event aggregator without Prism, you can use the package Autofac.Extras.CommonServiceLocator and register your container to ServiceLocator.
var builder = new ContainerBuilder();
var container = builder.Build();
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
I would use binding.
Add a dependency property to Zoomborder.
public bool? ResetZoom
{
get
{
return (bool?)GetValue(ResetZoomProperty);
}
set
{
SetValue(ResetZoomProperty, value);
}
}
public static readonly DependencyProperty ResetZoomProperty =
DependencyProperty.Register("ResetZoom",
typeof(bool?),
typeof(CloseMe),
new PropertyMetadata(null, new PropertyChangedCallback(ResetZoomChanged)));
private static void ResetZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool?)e.NewValue != true)
{
return;
}
ZoomBorder zb = (ZoomBorder)d;
zb.Reset();
zb.SetCurrentValue(ResetZoomProperty, false);
}
You can then bind that to a public bool property in your ContentViewModel.
When set to true, the border will reset.
This video shows how to create abstractions from Views/UI components and call methods on them from a VM using an interface. Don't let the title fool you. This look like it will fit this scenario perfectly
"How to Close Windows from a ViewModel in C#"
https://youtu.be/U7Qclpe2joo
Instead of calling the Close method on a window, you can adapt it to call your Reset method on your control from the VM.

Custom Usercontrol with MVVM and Catel

I've created a custom usercontrol that's composed of a AutoCompleteBox with a Selected Item... till now I've implemented it in a way I don't like... I mean I've a XAML view, a Viewmodel and in the viewmodel I load data from a stored procedure.
Since the AutoComplete box is a third party UserControl I've added it to the XAML view and not defined as a custom usercontrol. What's the best practice to do so?
I think the fact that I'm using Catel as MVVM Framework is irrilevant right now..
Thanks
UPDATE #1
My usercontrols need to have some properties that are passed via XAML for example (LoadDefaultValue)
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding Model.PortfolioModel}" Height="25" LoadDefaultValue="True" Width="150" />
To achieve such a scenario I had to define a dependency property in my PortfolioChooserView defined as
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
Since if I would have defined it in Viewmodel only I wouldn't have been able to set it.
The odd thing is that in order to pass it to the viewmodel I had to do such a trick
public PortfolioChooserView()
{
InitializeComponent();
if (!isFirstLoad) return;
Focusable = true;
PortfolioCompleteBox.AllowDrop = true;
PortfolioCompleteBox.Focus();
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
DataContextChanged += PortfolioChooserView_DataContextChanged;
isFirstLoad = false;
}
void PortfolioChooserView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataContext = DataContext as PortfolioModel;
if (dataContext != null)
{
dataContext.LoadDefaultValue = LoadDefaultValue;
dataContext.AllowNull = AllowNull;
//var converter = new PortfolioConverter();
//var portfolio = (Portfolio) converter.Convert(SelectedItem, null, null, CultureInfo.CurrentCulture);
//dataContext.SelectedItem = portfolio;
}
}
But I really dislike to use the DataContextChanged event ...do you see a better approach?
Thank
UPDATE#2
I keep this toghether since It's a related question...
On some viewmodel I used DeferValidationUntilFirstSaveCall = true; in the Constructor to disable the validation at load but my custom usercontrols shows the red border around... what should I do to propagate that info to the nested usercontrols?
Thanks again
See Orc.Controls for tons of examples. It's an open-source library that has a lot of user controls built with Catel, even one with an auto complete box.

How to databind two dependency properties belonging to two different controls?

Alternatively: How to subscribe to the PropertyChanged event defined by INotifyPropertyChanged thru the databinding of two dependency properties?
I have two separate user controls inside my main window. One control contains the parameters that affect the other control, let’s call it the display control. I want the parameter control to act as the datasource of the display control so that when I change a parameter in the parameter control, the display control be listening and reacts accordingly.
For this I created a class that implements INotifyPropertyChanged that stores these parameters and created dependencies properties of this class type in both controls. I was expecting that if I binded one control property to the other I would get the desired behaviour, but unfortunately I am missing something important because the display control is not reacting.
On a closer inspection with the debugger, I notice that my event PropertyChangedEventHandler PropertyChanged was always null when a property had changed, and everything I have read indicates, that no one is listening.
Because the display control is created in real time, I have to create the binding programmatically like this:
var DispayControlValuesBinding = new Binding();
DispayControlValuesBinding.Source = DisplayControlsControl;
DispayControlValuesBinding.Path = new PropertyPath("DisplayControlValues");
DispayControlValuesBinding.Mode = BindingMode.OneWay;
DispayControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
DispayControlValuesBinding.NotifyOnSourceUpdated = true;
//
graph.SetBinding(Graph.DisplayControlValuesProperty, DisplayControlValuesBinding);
Both controls have a dependency property called DispayControlValues. I try to bind the DisplayControlControl's DisplayControlValues property to the graph control's DisplayControlValues property.
When the application runs, it initializes the parameter control, then with a user request a display control is created programmatically and the binding is made. Then I change a value in the parameter control, this is catch by the parameters class that implements the INotifyPropertyChanged interface but because no one is listening, the event handler is null and here is where I am stuck.
Your help is greatly appreciated!
Here are more details as requested:
I have one user control that exposes the parameters that changes the behaviour of another control. This control has a dependency property that contains parameter details and implements the INotifyPropertyChanged interface.
Here is the class:
public class ZoomGraphControlValues : INotifyPropertyChanged
{
private bool _displayRaw;
public bool DisplayRaw
{
get { return _displayRaw; }
set
{
_displayRaw = value;
OnPropertyChanged(new PropertyChangedEventArgs("DisplayRaw"));
}
}
private bool _enableFit;
public bool EnableFit
{
get { return _enableFit; }
set
{
_enableFit = value;
OnPropertyChanged(new PropertyChangedEventArgs("EnableFit"));
}
}
public ZoomGraphControlValues()
{}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
Here is the dependency property:
public ZoomGraphControlValues ControlValues
{
get { return (ZoomGraphControlValues)GetValue(ControlValuesProperty); }
set { SetValue(ControlValuesProperty, value); }
}
public static readonly DependencyProperty ControlValuesProperty =
DependencyProperty.Register("ControlValues", typeof(ZoomGraphControlValues), typeof(ZoomGraphControls), new PropertyMetadata(null, OnControlValuesPropertyChanged));
private static void OnControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as ZoomGraphControls;
myObj.OnControlValuesPropertyChanged(e);
}
private void OnControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ControlValues != null)
{
IniValues();
}
}
Then I have the display user control. This control also implements a dependency property of the same type as the other control and I want this control to be the target of the binding, so that when I change values in the parameter control, this control reflect the changes.
Here is the dependency property of this control:
public ZoomGraphControlValues ZoomGraphControlValues
{
get { return (ZoomGraphControlValues)GetValue(ZoomGraphControlValuesProperty); }
set { SetValue(ZoomGraphControlValuesProperty, value); }
}
public static readonly DependencyProperty ZoomGraphControlValuesProperty =
DependencyProperty.Register("ZoomGraphControlValues", typeof(ZoomGraphControlValues), typeof(zoomGraph), new PropertyMetadata(null, OnZoomGraphControlValuesPropertyChanged));
private static void OnZoomGraphControlValuesPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObj = d as zoomGraph;
myObj.OnZoomGraphControlValuesPropertyChanged(e);
}
private void OnZoomGraphControlValuesPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ZoomGraphControlValues != null)
{
// update the control with the new parameters
ShowRawData(ZoomGraphControlValues.DisplayRaw);
SetChartBehabiour();
}
}
The Parameters control is initialized since the beginning of the application cycle. The display control gets created as per user request into a tab, so I have to create the control programmatically and thereby the binding as well:
//create the tab and wire tab events
//…
//create a display control
var graph = new zoomGraph();
// initialize the parameters class
var zgcv = new ZoomGraphControlValues
{
DisplayRaw = true,
ChartBehaviour = ChartBehaviour.Zoom
};
//assign the parameters class to the parameters user control dependency property
ZoomGraphControlsControl.ControlValues = zgcv;
//create the binding of the parameter control to the display control by linking their respective dependency properties
var zoomGraphControlValuesBinding = new Binding();
zoomGraphControlValuesBinding.Source = ZoomGraphControlsControl;
zoomGraphControlValuesBinding.Path = new PropertyPath("ControlValues");
zoomGraphControlValuesBinding.Mode = BindingMode.TwoWay;
zoomGraphControlValuesBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
zoomGraphControlValuesBinding.NotifyOnSourceUpdated = true;
zoomGraphControlValuesBinding.NotifyOnTargetUpdated = true;
graph.SetBinding(zoomGraph.ZoomGraphControlValuesProperty, zoomGraphControlValuesBinding);
//…
// add the user control to a tab
When I change a parameter in the parameter control I can see that it tries to fire the OnPropertyChanged event but it is always null. Because of this I think I am lacking something.
You are setting the binding mode to "OneWay" which means the view model will never get updated when the value changes in the view. Change the Binding mode to "TwoWay" and try again.
Also, check if you are changing the complete instance of "DisplayControlValues" or just properties on that class, because your binding is only set to fire when the entire instance changes, not its properties.
In addition to that, keep in mind that you can bind properties of two different controls using the Binding.ElementName property, which would make it unnecessary for you to create a view model, unless there is anything in the code behind you need to do when these values change.
If you post more code and XAML it will be easier to find the most appropriate way to solve your issue.

Trigger repaint of WPF Button control from external thread

I am having some issues with WPF not fully repainting a button control when the button is changed from another thread, and I am not sure how to force it to do a full repaint.
The situation is that on receipt of a message (via WCF - but the source isn't important, except that it is an external thread) I update the foreground color and visibility of a button. WPF immediately repaints the text on the button face, but the surface of the button is not repainted until I click anywhere on the application.
I have tried calling InvalidateVisual() on the button, but that did not help. I think that I am not understanding how a background thread can force a repaint. But the frustrating thing is that something is getting repainted and every other control I am using (text and image controls) are also getting properly repainted when I update them from my same message receipt.
I have now tried sending an empty message to the Dispatcher of the application via Invoke(), but no luck there either.
So I am looking for tips on how to tell WPF that it needs to update the rest of the button and not just the text.
Edit
This is a rough skeleton of my program. Note that I have wrapped the button in a class as there is other related state information I am keeping with it.
class myButton
{
Button theButton
void SetButton()
{
theButton.Forground = a new color
}
}
main
{
myButton.theButton = (Button on WPF canvass)
RegisterCallback( mycallbackFunction) with WCF client endpoint
}
void myCallbackFunction(message)
{
if message has button related stuff, call myButton.SetButton
}
Edit 2
Solved my problem .. it was actually a conflict between a "CanExecute" method and setting the buttons attributes in the callback. Once I removed the "CanExecute" function it all worked.
Setting properties on the button itself from code, especially another thread/callback, is an entrance to a painful world of inconsistent states.
What you should do is bind your button's properties to properties in your code, and then have your callback change those external properties.
I know the code you posted was kind of a mock up for what you actually want to do in your program, and I couldn't really follow your logic, but here's a complete program that operates similarly to your example and shows what I'm talking about. Let me know if I've missed the mark.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public class MyButton : INotifyPropertyChanged
{
private Button _theButton;
public Button TheButton
{
get { return _theButton; }
set
{
_theButton = value;
//set text binding
Binding textBind = new Binding("Text");
textBind.Source = this;
textBind.Mode = BindingMode.OneWay;
_theButton.SetBinding(Button.ContentProperty, textBind);
//set color binding
Binding colorBind = new Binding("Brush");
colorBind.Source = this;
colorBind.Mode = BindingMode.OneWay;
_theButton.SetBinding(Button.ForegroundProperty, colorBind);
NotifyPropertyChanged("TheButton");
}
}
public void Set(string text, Brush brush)
{
this.Text = text;
this.Brush = brush;
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged("Text"); }
}
private Brush _brush;
public Brush Brush
{
get { return _brush; }
set { _brush = value; NotifyPropertyChanged("Brush"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
internal void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public partial class MainWindow : Window
{
MyButton _myButton = new MyButton();
public MainWindow()
{
InitializeComponent();
//button1 is defined in XAML markup
_myButton.TheButton = this.button1;
//or else this could be your callback, same thing really
Thread t = new Thread(SetButton);
t.Start();
}
void SetButton()
{
_myButton.Text = "wo0t!";
_myButton.Brush = Brushes.Red;
//or
_myButton.Set("giggidy!", Brushes.Yellow);
}
}
}
Note that binding your Button properties in XAML is much less ugly, but then we're getting into UserControls and DataContexts which is another topic. I would look at inheriting the Button class to implement the features you want.
I recommend reading the article (Build More Responsive Apps With The Dispatcher) from MSDN magazine that describes how WPF works with the Dispatcher when using BackgroundWorker.
As per my edit, I had conflict between the buttons CanExecute binding in the XAML and me setting the background color in the callback. I didn't really need the CanExecute, so getting rid of that solved my problem.

WPF datagrid multiple windows question

I have a scenario where i load an ICollectionView in a datagrid.
In some cases I modify the data where the collectionview gets it's data from. If I then reload the grid with configGrid.ItemsSource = configData; for example, the data gets updated.
Now the thing is, I sometimes open a new window using:
var newWindow = new Edit(movie);
newWindow.Show();
The thing is, I also edit the data using this new window. Now I want the datagrid in the first window to be refreshed after I close this second window (actually, it doesn't matter when it gets refreshed, as long as it does).
How do I do this?
I might be missing something here (I have a crippling hangover unfortunately) but can't you handle the window closed event of newWindow and refresh confiGrids itemsource there?
Window newWindow = new Window();
newWindow.Closed += new EventHandler(newWindow_Closed);
newWindow.Show();
void newWindow_Closed(object sender, EventArgs e)
{
configGrid.ItemsSource = configData;
}
If the collection behind the ICollectionView supports INotifyCollectionChanged (like ObservableCollection) and the object itself supports INotifyPropertyChanged then the grid is supposed to update automatically
Otherwise you are on your own and the editing window should raise some sort of notification (maybe an event) that you should receive and update the list.
Ok, here's the long version:
WPF data-binding can update the UI automatically - but it needs to know that something changed in order to trigger the update, the easiest way to do this is to support INotifyPropertyChanged, let's create simple class:
public class Movie
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Now, let's add INotifyPropertyChanged support:
public class Movie : INotifyPropertyChanged
{
public event PropertyChanged;
protected virtual OnPropertyChanged(string property)
{
var ev = PropertyChanged;
if(ev!=null)
{
ev(this, new PropertyChangedEventArgs(property));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
Now when you bind to the movie class and change the Name property the UI will be updated automatically.
The next step is to handle a list of Movie objects, we do that by using a collection class the implements INotifyCollectionChanged, luckily for us there's one already written in the framework called ObservableCollection, you user ObservableCollection<T> the same way you would use a List<T>.
So, just bind to ObservableCollection and WPF will automatically detect when objects change or when they are added or removed.
ICollectionView is very useful, it adds support for current item, sorting, filtering and grouping on top of the real collection, if that collection is an ObservableCollection everything will just work, so the code:
ObservableCollection<Movie> movies = new ObservableCollection<Movie>();
ICollectionView view = CollectionViewSource.GetDefaultView(movies);
will give you a collection view that supports automatic change notifications.

Resources