WPF binding to send complex state from View to Viemodel - c#

Sadly, this might need a little background, so my apologies up front for the loquacity; here's what I have first: I have created a "MapCanvas" WPF control derived from Canvas that allows the user to draw and manipulate shapes (which it creates and manages as a list of Path instances). Users can draw, (multi-)select, rotate, scale, group, translate and clone such shapes, which display over an Image background hosted in the Canvas-derived control. The idea is to allow hotspots of interest to be drawn over the image of a map, with customisable data attached to their Tag properties. Everything can be translated or scaled (both the map Image and all the shapes drawn over it) using separate cached TranslateTransform, RotateTransform and ScaleTransform instances applied in a group to the Canvas' RenderTransform, so all the shapes and the underlying background image transform together. This all works fine.
What I want to do is to then move this MapCanvas control into another larger ongoing MVVM project as a View, and bind a ViewModel to it. Since the control has some fairly complex internal state to manage all the shape drawing and manipulation (and the usual fun and games with feeding keyboard and mouse events back from the host window to the canvas-derived control since Canvas is picky about when it will respond to them) there is quite a bit of code-behind in the control.
Ideally, yes, I know, that logic would live in the ViewModel and talk to the View through binding, but the Canvas needs to know about key, left & middle mouse up and down events as well as mouse movement to create and manipulate the Paths, and just getting those events to the control is not straightforward; I'm not in love with the code-behind solution, but it works. If anyone has a better idea, I'm all ears.
Anyway, I do at least want the Canvas' list of those Paths to be available to the ViewModel so that everything else in the larger project can be done with them properly through MVVM. Not too hard, I thought; expose the list of paths as a DependencyProperty. Since most of the changes involve simply adding to that list (that is, accessing but not assigning the list) I factored out methods for AddNewPathToList() and ClearPathList() which implement INotifyPropertyChanged so that the ViewModel will be advised if a path is added etc.
Then just bind a public property on the ViewModel back to the DependencyProperty on the View, right?
The code for the View (the MapCanvas control) is pretty long, so I won't post it. BTW if anyone is interested, I am happy to share that code; it works just fine, but is implemented with code-behind like an olde-fashioned Windows Forms control. I know that I am condemning my soul to the eternal fires for this. C'est la vie.
Control/View embedding in the main window:
<Window.DataContext>
<local:MapCanvasVM/>
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Path=ViewModelPathSelectionInfo}" Height="20" VerticalAlignment="Top"/>
<local:MapCanvas x:Name="mpcControl" ClipToBounds="True" Background="Gray" Margin="0,20,0,0" MultiSelectPath="{Binding Path=SelectedPaths, Mode=OneWayToSource}" NumSelectedPaths="{Binding Path=NumSelected, Mode=OneWayToSource}"/>
</Grid>
The TextBlock is just there for debugging; I can watch this in the View to see how many paths the ViewModel thinks there are selected.
The bindings are OneWayToSource since the View only ever tells the ViewModel what paths it currently has selected, never the other way; the paths are created and manipulated directly by the MapCanvas in response to mouse and key events. I know this isn't proper MVVM, but it was just too hard to even think about how to do all that. Instead, I just encapsulated everything into the control; you can drop it into a window and it works. To be honest, I've never tried MVVM in this direction (purely View to ViewModel) before, and I'm not even convinced it is the right way to go. But the project I want to drop this into will use these annotated maps as part of a much larger set of data already managed through MVVM.
Here are the DependencyProperties and modifier methods in the MapCanvas View:
public static readonly DependencyProperty MultiSelectPathProperty = DependencyProperty.Register("MultiSelectPath", typeof(List<Path>), typeof(MapCanvas));
public static readonly DependencyProperty NumSelectedPathsProperty = DependencyProperty.Register("NumSelectedPaths", typeof(int), typeof(MapCanvas));
and
protected void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
/// <summary>
/// Provides backing access to the MultiSelectPathProperty DependencyProperty exposing the list of currently selected Path instances; raises PropertyChanged for assignment but not changes through access; all modifying access should be made through AddToMultiSelectPath() or ClearMultiSelectPath() which also raise PropertyChanged once changes are made.
/// </summary>
public List<Path> MultiSelectPath
{
get
{
return (List<Path>)GetValue(MultiSelectPathProperty);
}
set
{
if (MultiSelectPath != value)
{
SetValue(MultiSelectPathProperty,value);
OnPropertyChanged("MultiSelectPath");
}
}
}
/// <summary>
/// Provides backing access to the NumSelectedPathsProperty DependencyProperty exposing the number of currently selected Path instances
/// </summary>
public int NumSelectedPaths
{
get { return (int)GetValue(NumSelectedPathsProperty); }
set
{
if (NumSelectedPaths != value)
{
SetValue(NumSelectedPathsProperty, value);
OnPropertyChanged("NumSelectedPaths");
}
}
}
/// <summary>
/// Adds a path to the multiselection and raises PropertyChanged to notify observers of that property that it has changed through access (normally only assignment triggers the event)
/// </summary>
/// <param name="NewPath"></param>
private void AddToMultiSelectPath(Path NewPath)
{
MultiSelectPath.Add(NewPath);
OnPropertyChanged("MultiSelectPath");
NumSelectedPaths = MultiSelectPath.Count;
}
/// <summary>
/// Clears the multiselection and raises PropertyChanged to notify observers of that property that it has changed through access (normally only assignment triggers the event)
/// </summary>
private void ClearMultiSelectPath()
{
MultiSelectPath.Clear();
OnPropertyChanged("MultiSelectPath");
NumSelectedPaths = MultiSelectPath.Count;
}
Updating the number of paths in the selection in the AddToMultiSelectPath(Path NewPath) or ClearMultiSelectPath() methods through the NumSelectedPaths setter raises the appropriate PropertyChanged event there, too, as you'd expect.
So far, so good.
Now here is the ViewModel, and in the comments you'll see why I'm so dumbfounded:
internal class MapCanvasVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Path> mSelectedPaths = new List<Path>();
private int mNumSelected;
public MapCanvasVM()
{ }
private void OnPropertyChanged(string PropertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
public List<Path> SelectedPaths
{
get { return mSelectedPaths; }
set
{
if (mSelectedPaths != value)
{
mSelectedPaths = value;
// Huh ?!
// a breakpoint in this setter fires twice with new list (MapCanvas initialiser) then NULL (from InitializeComponent()?),
// but then with an empty list for the first time a path is added to the selection, then never again.
// BUT THE UNDERLYING PROPERTY IS CORRECTLY SET AT ALL TIMES.
// how is that even possible???
}
}
}
/// <summary>
/// Bound from VM back to a TextBlock on the View for debugging
/// </summary>
public string ViewModelPathSelectionInfo
{
get { return SelectedPaths.Count.ToString(); }
}
/// <summary>
/// Bound from View to VM
/// </summary>
public int NumSelected
{
get { return mNumSelected; }
set
{
if (mNumSelected != value)
{
mNumSelected = value;
// we don't actually care about this value, but knowing that it has changed means we know that the
// SelectedPaths property has also been updated, since for some reason that setter isn't behaving itself.
OnPropertyChanged("ViewModelPathSelectionInfo"); // let the View know we want to update the count of paths in the debugging TextBlock
var Dummy = SelectedPaths; // What??? SelectedPaths is CORRECTLY SET if I break here!!!
}
}
}
}
Well, no. That doesn't work, but its doesn't work in the strangest way, as noted in the comments above. Well, actually, it does seem to work, but I don't trust it an inch.
You can see that to test the bindings, I created an integer-valued NumSelectedPathsProperty DependencyProperty on the View that simply exposed the number of selected paths in the control's list (which is itself the other List-valued MultiSelectPathProperty DependencyProperty). When I bound NumSelectedPaths to the NumSelected property in the ViewModel using
NumSelectedPaths="{Binding Path=NumSelected, Mode=OneWayToSource}"
it works perfectly.
So why can I pass an integer from the View to the ViewModel but virtually identical code to pass a List using exactly the same setup has such weird behaviour?
I mean, it works, but stepping the code when I add a path to the selection in the MapCanvas only ever trips the setter once (with the wrong value) and never breaks again, even though breaking on the OTHER bound property and examining the runtime value of SelectedPaths shows it has the correct value every time, even though a breakpoint on the setter doesn't get hit.
So, finally, the question:
a) has anyone ever seen anything like this, or have any idea what is going on with the bindings behaving like that?
b) surely there is a better way to do this MVVM binding between the control and the ViewModel, yes? Advice?
c) in general, to get state from the View back to the ViewModel (not the other way, as virtually all the tutorials, code snippets and articles I've found explain) is there a general approach that is different to and better than what I've used here?
Thank you all so much in advance for reading all that, and for any kind words of advice.

Related

Prism.WPF: Change MainWindow Window.Effect on Navigation

The application is a Prism Application in WPF using C#.
I am attempting to assign a BlurEffect to the Window.Effect property when a button is clicked on the navigation menu.
I have the Window.Effect bound to a property in my viewmodel.
<Window ... other properties ..
Effect = {Binding Fuzzy}>
and the Fuzzy property in the ViewModel.
private Effect _fuzzy;
public Effect Fuzzy { get => _fuzzy; set => SetProperty(ref _fuzzy, value); }
What I am attempting to implement is that when a button is clicked on the navigation menu that the window will blur while a UserControl is loading.
I have tried to implement the change in the Navigate method.
private void Navigate(string viewName)
{
PerformBlur();
_regionManager.RequestNavigate("ContentRegion", viewName);
}
private void PerformBlur()
{
BlurEffect blur = new BlurEffect();
blur.Radius = 4;
var ef = blur;
_fuzzy = ef; //I've tried Fuzzy = ef too
}
But that doesn't work.
I need to make the change to the window effect before it attempts to navigate, and I haven't been able to figure out how to make that happen. I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM. Any suggestions on how to implement this functionality would be greatly appreciated.
(Bonus points if you can tell me how to animate the blur. lol)
I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM.
Invoking the command programmatically from the code-behind of the view is not any worse than invoking it from the XAML markup of the very same view as far as MVVM is concerned.
MVVM is not about eliminating code from the views. It's about separation of concerns. You can implement an entire view programmtically in a C# without using XAML at all and still be fully compliant with MVVM.
Trying to do fairly complex stuff in XAML just because you possible can is generally considered as an antipattern. Remember that XAML is a markup language. C# is a much more expressive and concise language so if you can solve your issue by writing some code, then this is most probably what you should do.
Taking a look at the prism source code, I can see iregionmanager is full of abstracted interfaces.
( Wow. I don't know why it still surprises me but prism is very complicated ).
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegion.cs
That includes IRegionNavigationService
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegionNavigationService.cs
You could therefore override pretty much any functionality you like, if you wanted to.
Notice though, the two events :
/// <summary>
/// Raised when the region is about to be navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigating;
/// <summary>
/// Raised when the region is navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigated;
Looks to me like "all" you need is a reference to your region navigation service in the view.
Handle those two events to set blur then remove blur.
You could then do navigation in code behind or viewmodel. Whichever suits.
If you wanted to decouple viewmodel from view, you could use the eventaggregator.
There is another option though.
You don't explain exactly what you have there. So let's imagine and consider a better way to do this.
Say you have a set content of a set control you're always navigating. That's being switched out as you navigate for a new view whose datacontext is a new viewmodel.
You could bind an attached property from the window to the datacontext of that.
In that property you can have a change callback.
In a base viewmodel you could add an IsLoaded bool property which is initialy false.
When your dependency property callback returns null or false then you blur.
You change the viewmodel property to false in the current viewmodel when you start to navigate. The window blurs. The content is switched out and you get a new viewmodel. Once navigation completes you set that ILoaded true. You callback un blurs the window.

WPF: Button if clicked runs a new query and updates the DataGrid with the new query

What I want to do is access the database via a query (already have one made, but heres the issue:
namespace WpfApp3
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class ViewData : Window
{
public ViewData()
{
InitializeComponent();
}
private void ShowData(object sender, RoutedEventArgs e)
{
CustomerEntities1 db = new CustomerEntities1();
if (report.IsInitialized)
{
var report = from values in db.Customers
select values;
valueGrid.ItemsSource = report.ToList();
}
else if (sortName.IsInitialized)
{
var sortName = from values in db.Addresses
select values;
valueGrid.ItemsSource = sortName.ToList();
}
}
}
}
I Do not understand how the bindings in WPF work, and Im having issues when running the code. It only runs the top portion of the if condition. Any ideas what binding I should use where if clicked, it is considered true, and runs the condition when met. Once the condition is met, it should remove the old one, and replace it with the new query for the DataGrid. Thank You
So in WPF the UI Components (valueGrid) in your case are usually notified if the Data changes (CustomerEntities1) on Initialization and IF and ONLY IF the Source implements INotifyPropertyChanged . you should therefore consider implementing this interface in ally or ViewModels, assuming you are using the MVVM pattern.
Any ideas what Binding I should use
The way your code is set up you can't use bindings at all but you would want a oneWay binding if all you do is populate your DataGrid from code.
If you want to use Data binding you need to set the DataContext of your Window to something like
DataContext = this;
This will allow you to bind properties that you declare in your code behind to UI controls in your .xaml code like this:
<TextBlock Text="{Binding PropertyName}"/>
Now this might work syntax wise but your DataGrid still won't update because your DataContext class or any of their parent classes need to Implement the INotifyPropertyChanged Interface.
I advise you to start reading about the basics of Data binding and work through a tutorial or two. Because even if you follow what I've written above and you get it to work it is not the ideal approach to this problem.
You'd want to set up a type of class known as ViewModel that will be have all the properties that hold the data you want to show in your View (the Window) and have it as the Datacontext.
This link will give you a good overview of what data binding is, what you can do and how get it to work.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-6.0

Binding vs x:Bind: Why isn't the view updated when using Binding?

I am developing an UWP app leveraging the MVVM paradigm. My view contains a simple TextBox that has its Text property bound to a respective ViewModel property:
<TextBox Text="{Binding Path=Radius, Mode=TwoWay}"/>
Naturally, I've assigned my ViewModel to the page's DataContext:
public sealed partial class ExamplePage : Page
{
private ExamplePageVM viewModel;
public ExamplePage()
{
this.InitializeComponent();
viewModel = new ExamplePageVM();
DataContext = viewModel;
}
}
In the ViewModel I perform some kind of input validation, i. e. if the user inserts an invalid float value into the TextBox I want to reset the TextBox to a default value (zero, for instance):
class ExamplePageVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private float radius;
public string Radius
{
get => radius.ToString();
set
{
if (radius.ToString() != value)
{
if (!float.TryParse(value, out radius)) radius = 0;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Radius)));
}
}
}
}
Changing the value in the TextBox causes the setter to be called as intended. Also, the PropertyChanged event is invoked accordingly. However, the TextBox still contains invalid data after the setter execution has finished, which means that the view isn't updated correctly.
According to the first comment on this post, the solution to this issue is using <TextBox Text="{x:Bind viewModel.Radius, Mode=TwoWay}"/> instead of the Binding approach shown above. Why is that so? What's the difference between Binding and x:Bind in this very situation?
You may want to set the UpdateTrigger yourself since TextBox normally updates the source when focus lost gets called.
You can change the behaviour UpdateSourceTrigger=PropertyChanged.
<TextBox Text="{x:Bind AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
If this is not working you may want to prevent inputs different then numbers with the keydown event. Which you could outsource in a user control for reuse.
Hope this helps.
Binding to TextBox.Text is a rather special case, because Microsoft made a decision where the most common scenario is that the binding should be updated when control loses focus, as opposed to each and every input text change. This allows 2 things:
somewhat more efficient handling of larger texts
safeguarding user input in progress from being changed by application
In the absence of publicly available UWP source code it's possible that MS developers may provide you with more reliable insight, but even comparing the changes to a bound source with direct tracking of the EditBox.TextProperty via DependencyObject.RegisterPropertyChangedCallback makes you expect that instead of the usual direct binding to the dependency property changes there is actually an additional man-in-the-middle kind of implementation in TextBox that handles how and when TextProperty updates affect and being affected by the DataContext or underlying class properties bound with {Binding} or {x:Bind}.
Note that {x:Bind} and {Binding} are very different mechanisms, especially with the first being a compile-time and the 2nd is run-time, meaning that internally they require different implementation and it's up to the framework developers to make sure they exhibit the identical behavior.
Now in your test scenario you are trying to validate and possibly change a property value in the bound data source expecting that the TextBox will display the value you want, which it does with {x:Bind}, but not with {Binding}.
Apparently you've found a scenario where {x:Bind} and {Binding} implementations behave differently. I did the same tests and totally confirm your findings.

MVVM when to create a viewmodel for a control?

Or should I only create viewmodels for the domain data being represented? While reading on MVVM, I came across this:
"The ViewModel is responsible for these tasks. The term means "Model of a View", and can be thought of as abstraction of the view, but it also provides a specialization of the Model that the View can use for data-binding. In this latter role the ViewModel contains data-transformers that convert Model types into View types, and it contains Commands the View can use to interact with the Model. "
http://blogs.msdn.com/b/johngossman/archive/2005/10/08/478683.aspx
If the viewmodel is a model of the view, then doesn't it make sense to put properties of the view in the viewmodel rather than on the code behind of the view itself?
I guess in making a custom control I just have a hard time deciding when I should just add a property to the control's code behind and when it is worthwhile to make a viewmodel for the control to represent it. Honestly I kind of feel that moving all of the control's view related properties to the viewmodel would clean up the code behind of the control leaving only the control logic.
However, if I were to change things like this, then at times when an item needs properties from the control itself I can no longer use {Binding ElementName = control, Path=property} and have to instead get the data context of the parent (because the current datacontext would be on the individual subitem of the observable collection.
Basically I was considering whether I should move properties from Class GraphViewer into a GraphViewerViewModel and then just bind to it.
Code is worth a million words so:
public class GraphViewerViewModel :DependencyObject
{
private const int DEFAULT_PEN_WIDTH = 2;
private const int DEFAULT_GRAPH_HEIGHT = 25;
public SignalDataViewModel _SignalDataViewModel
{
get;
set;
}
public PreferencesViewModel _PreferencesViewModel
{
get;
set;
}
}
Meanwhile
public class SignalDataViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
ObservableCollection<SignalViewModel> _signals;
public ObservableCollection<SignalViewModel> Signals
{
get
{
return _signals;
}
private set
{
_signals = value;
}
}
ObservableCollection<SignalViewModel> _AddedSignals;
public ObservableCollection<SignalViewModel> AddedSignals
{
get
{
return _AddedSignals;
}
private set
{
_AddedSignals = value;
}
}
it is a pain to type:
PenWidth="{Binding RelativeSource = {RelativeSource AncestorType={x:Type DaedalusGraphViewer:GraphViewer}},
Path = _GraphViewerViewModel._SignalDataViewModel._AxisDivisionUnit.GraphPenWidth, Mode=OneWay}"
and I'm wondering if it is worthwhile to make the change or whether I'm misunderstanding what a view model should be used for in mvvm.
I guess in making a custom control I just have a hard time deciding when I should just add a property to the control's code behind and when it is worthwhile to make a viewmodel for the control to represent it. Honestly I kind of feel that moving all of the control's view related properties to the viewmodel would clean up the code behind of the control leaving only the control logic.
In general, a custom control is 100% View layer code. As such, it really falls outside of MVVM entirely.
The main goal when making a custom control to be used within an application being designed with MVVM is to make sure that you design and build the custom control in a way that it is fully compatible with data binding. This will allow it to be used within your View layer of your application exactly like other controls.
As such, this pretty much guarantees that you'll have code behind, since implementing Dependency Properties really requires code behind. You also don't want to set the DataContext of a custom control within the control (since you want to inherit the data context of the user control or window using the control).
Basically I was considering whether I should move properties from Class GraphViewer into a GraphViewerViewModel and then just bind to it.
If the types are specific to your domain, then this is really typically more of a UserControl being used by your application. In that case, creating a ViewModel and just binding is likely good.
If this is, on the other hand, a true custom control that's made to be completely general purpose (ie: usable by anybody in any application), then keeping it as a "pure view" custom control typically means that you 1) won't take a dependency on any ViewModels or domain specific objects, and 2) not set the data context (which means no view model).

WPF Databound Property Loosing value

Hi I have a viewmodel where i can track the value of a certain item in the constructor. I am opening a dialog window using the MVVM model.
example
private int _myField;
public ClassName(int MyProperty)
{
_myField = MyProperty;
}
public int MyIntProperty
{
get{ return _myField;}
set { _myField = value;}
}
this is all perfect obviously.
but as soon as the window opens the value in the viewmodel changes.
lets say the _myField goes from 1 to 8 with out any interaction. i've walked through the code and there are no other interactions with the field.
also not in the code sample is the bound property.
anyone every came accross this before. it has me stumped.
Edit: included missing property from example
You should either:
1) Implement INotifyPropertyChanged on ClassName. This will allow you to raise the PropertyChanged event when you change MyIntProperty. WPF will listen to this event and update the UI accordingly.
or
2) Make ClassName inherit from DependancyObject and MyIntProperty a dependency property. This will take care of everything for you.

Categories

Resources