Access DataContext of Page from MainWindow (with Telerik) - c#

I am relatively new to WPF and I have stumbled across a problem that I just can't seem to find a solution for.
I am sure that there is already a thread concerning a problem like that but in regard of my lacking knowledge it is very likely that I haven't found it or simply did not understand it.
My problem:
I am developing a WPF-application in C#. It's an Outlook-Styled application with a big MainWindow with a huge ViewModel and XAML.
What I was trying to do, is to split up the single codefiles a bit to make it a little bit more modular and compact.
I am using Telerik Controls and tried to outsource the content of single SplitContainers into Pages, which worked fine until now.
Today, a new situation came up which is somehow stupid and wasn't looking too complicated, but somehow I can't get it to work.
Situation:
I have a Treeview in my "MainWindow" and whenever I change the selection in there, I want to change a property on my Page that I have made a binding to.
So, when I click on an item in "TreeView_3" I want to set a property via EventHandler (SelectionChanged_TreeView3) on the DataContext of "Page_X".
If I had to do this on the MainWindow, I would typically do it like that:
UserViewModel uvm = mainGrid.DataContext as UserViewModel;
Then just call whatever property of specific UserViewModel (ViewModel of the MainWindow) I want to access.
I can't do this the same the same way for the page obviously since "mainGrid.DataContext" will always refer to the MainWindow, since this is where the eventhandler is called.
So what I need would be a little explanation on how to access the DataContext from a page with a different ViewModel.
If you need any code in order to explain, let me know.

You need to separate your concerns. In your code behind your should have only code that handles view related stuff. Most often my codebehind is empty.
In your ViewModels you should handle your data related logic. So instead of casting the datacontext in your code behind, handle a click with a Commandin your viewmodel.
Since there is no possibility to bind a command to the SelectedItemChanged of your TreeView you can use an interaction trigger.
<TreeView xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding Path=SomeCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>

Ruven it is hard to say without some example code. But it could be that you need to implement INotifyPropertyChanged on the ViewModels?
https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-implement-property-change-notification
By calling OnPropertyChanged("PropertyName"); in the setter of a viewmodel property the ui will pick up the change.
Also make sure both views are referencing the same object and not copies of the same object.

Related

Is manipulating view objects from view models a standard in Prism projects?

I have very little experience with Prism and .NET (< 1 year). Yet, I am working on a project where those technologies are used extensively. Also, I am an experienced developer, but in other frameworks, other programming languages, etc. Therefore, I have sometimes stupid questions to ask like the following one.
So, we are currently trying to build up a client application that shows a grid of data along with buttons allowing for the usual CRUD operations. The team's ansatz is the following:
We create a xaml view and define the GridControl like this:
<dxg:GridControl x:Name="MyGrid"
ItemsSource="{Binding Items}"
SelectedItems="{Binding SelectedItems}">
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="Loaded"
Command="{Binding GridLoadedCommand}"
CommandParameter="{Binding ElementName=MyGrid}" />
</dxmvvm:Interaction.Behaviors>
[...]
This triggers command GridLoadedCommand upon view loading. GridLoadedCommand stores the GridControl object in the view's data context, which is the view model.
Upon clicking the DeleteAll button, the view model's DeleteAllCommand calls a method of the following kind:
public static void DeleteAllRows(GridControl gridControl)
{
gridControl?.SelectAll();
DeleteSelectedRowsEx(gridControl);
}
In essence, that is perfectly valid code and is also working like a charm. However, in my opinion, but I might be mistaken, that kind of code goes against MVVM philosophy because in the above snippet we are clearly mixing up "view" with "view model" code. Indeed, the view model manipulates a GridControl object directly, which, as far as I know, should be avoided as much as possible, as it breaks "separation of concerns" and makes the software design entangled. Also, it might complicate unit testing a bit. Am I seeing that right or am I alien here? I would rather manipulate the ViewModel.Items and ViewModel.SelectedItems observable collections directly and trigger the relevant events to update the view instead.
Now, the actual reason why the team came up with that code is because they wanted a generic base view model class with generic CRUD operations, irrespective of what the underlying ItemsSource abd SelectedItems (see GridControl xaml code above) are. Is there a standard way to make that happen in the Prism constellation? I would just add a generic parameter to the base class with the ItemsSource's data type and pass the specific ItemsSource and SelectedItems to the generic class. Is there a better way? Is the way I've described here above with direct GridControl manipulation from the view model really the way to go?

MVVM Displayer OberservableCollection<ViewModel> with unknown UserControl

im a little bit stuck with my current project and hope someone can help me out of this.
My application works with plugins so that the user is able to add additional functionality to the application. Now i would like to have the configuration window in the same style (Maybe a plugin need some kind of configurations).
The configuration window loads all plugins and get the configuration ViewModel from the plugins. All the ViewModels are stored in an ObservableCollection. These ViewModels should be displayed in a TabControl (one tab per ViewModel)
But i dont know the type of UserControl the plugin is using, because the plugin come up with its own UserControl for configuration purposes.
Otherwise i would create a TabControl, bind its ItemsSource to the ObservableCollection and specify the UserControl in the Resources (DataTemplates).
But how to do it in case the UserControls are unknown to compile time?
I thought about using a ObservableCollection instead of ViewModels, but im not realy happy with that and even dont know if this will work.
Do you have some idea how to deal with that?
Kind regards,
SyLuS
You could use a ContentControl to achieve this.
It's used to show views depending on their viewmodel.
In your xaml you can specifiy which view should be shown. Based on the viewmodel which is the current DataContext.
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyView/>
</DataTemplate>
</<ContentControl.Resources>
</ContentControl>
But when you say you are using a plugin system, maybe something like PRISM, you have to setup the datatemplates automatically. Never done this before. But maybe I gave you a point where you can start.

System.Windows.Controls.UserControl Loaded Event firing when 'Unloaded'

I'm getting a strange phenomenon where the UserControl Loaded Event is firing when the parent window's content control is changing from the current to a new one.
I've tested this behaviour on multiple UserControls and it's happening on all of them.
What I've done:
Window:
<xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"/>
<Controls:TransitioningContentControl Transition="RightReplace" Content="{Binding CurrentViewModel}"/>
When I change the CurrentViewModel Property the corresponding View is loaded into the content control.
UserControl:
<xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding Load, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
When the UserControl is loaded (and for some reason when the UserControl is changed to another) the Load method is run.
Possible Solutions:
There are ways I could work around this for example I could create a Boolean property called ShouldLoad with an if formula in the Load method however this seems rather convoluted for what I'm trying to achieve.
I feel that this behavior should not happen however there is probably an explanation for this...
I'm currently encountering this issue in my app as well.
So far I have found that it has something to do with the <Controls:TransitioningContentControl>, how it is used or where it is placed. I changed my UI to only use <ContentControl> and the multiple Loaded events are not happening anymore for me. It also seems to work as expected for the <Controls:MetroContentControl>.
This helped me a lot... As it turns out the loaded event gets fired in many circumstances such as when the tab control is changed.
http://blogs.msdn.com/b/mikehillberg/archive/2006/09/19/loadedvsinitialized.aspx

MVVM Light - Multiple ViewModels (and connecting them up)

I am trying to learn the MVVM pattern (C#), having come from a Windows Forms background. I am using the MVVM Light toolkit, and so far I think it is brilliant.
I have made several small applications, however one thing I am struggling with is introducing a second view.
I want to (for example), have a button on my MainViewModel, which via a RelayCommand, opens up a new Window - let's say an "About" window. I have done hours of research on the web for this however it seems I can't get my AboutViewModel to communicate with/show my AboutView.
I have placed a receiving messenger in the code-behind constructor of the AboutView.xaml - however I can't get it to receive any messages from the AboutViewModel, and thus can't make it 'Show()'.
If anyone has an example of an Mvvm Light WPF app using multiple views that would be great :)
There are two ways I can think to do this easily
The first would be to use a Popup instead of a new Window. For example, I often put properties in my ViewModel for PopupContent and IsPopupVisible, and set those values anytime I want to display my Popup control. For example, a ShowAboutPopup relay command might run something like this:
void ShowAboutPopup()
{
PopupContent = new AboutViewModel();
IsPopupVisible = true;
}
You can display it using a Popup object, or a custom UserControl. I prefer to use my own custom Popup UserControl, which will usually end up looking like this:
<Window>
<Canvas x:Name="RootPanel">
<SomePanel>
<!-- Regular content goes here -->
</SomePanel>
<local:PopupPanel Content="{Binding PopupContent}"
local:PopupPanel.IsPopupVisible="{Binding IsPopupVisible}"
local:PopupPanel.PopupParent="{Binding ElementName=RootPanel}" />
</Canvas>
</Window>
The PopupContent property is a ViewModel (such as an AboutViewModel), and DataTemplates are used to tell WPF to draw specific ViewModels with specific Views
<Window.Resources>
<DataTemplate DataType="{x:Type local:AboutViewModel}">
<local:AboutView />
</DataTemplate>
</Window.Resources>
The other method is to have some kind of ApplicationViewModel that runs on startup, and is responsible for the overall application state, which includes which window(s) are open.
Typically I prefer to have a single ApplicationView that contains a ContentControl to display the current page
<Window>
<ContentControl Content="{Binding CurrentViewModel}" />
</Window>
however it can also be used to manage multiple windows. If you do use it to manage multiple Window objects, be warned that this will not be a pure ViewModel because it will need to access some View-specific objects, and referencing UI objects it not something a ViewModel should do. For example, it may subscribe to receive ShowWindow messages, and upon receiving those messages it would create the specified View and show it, and possibly hide the current window as well.
Personally, I try to avoid multiple windows as much as possible. My usual method is to have a single View that contains consistent application objects for any page, and a ContentControl containing dynamic content that changes. I have an example using this navigation style on my blog if you're interested
As i can see you want a navigation in your MVVM app?
Word goes to the creator of MVVM Light - Laurent Bugnion - with his post about using Navigation Service for switching Views. It's actually about Windows Phone & Silverlight but same should apply to WPF.
Also this answer in related question uses this approach.

Is there an MVVM-friendly way to swap views without value converters firing unnecessarily?

I thought what I was doing was right out of the Josh Smith MVVM handbook, but I seem to be having a lot of problems with value converters firing when no data in the view-model has changed.
So, I have a ContentControl defined in XAML like this:
<ContentControl Grid.Row="0" Content="{Binding CurrentViewModel}" />
The Window containing this ContentControl references a resource dictionary that looks something like this:
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type lib_vm:SetupPanelViewModel}">
<lib_v:SetupPanel />
</DataTemplate>
<DataTemplate DataType="{x:Type lib_vm:InstructionsPanelViewModel}">
<lib_v:InstructionsPanel />
</DataTemplate>
</ResourceDictionary>
So, basically, the two data templates specify which view to show with which view-model.
This switches the views as expected whenever the CurrentViewModel property on my window's view-model changes, but it also seems to cause value converters on the views to fire even when no data has changed. It's a particular problem with IMultiValueConverter classes, because the values in the value array get set to DependencyProperty.UnsetValue, which causes exceptions unless I specifically check for that. But I'm getting other weird side effects too.
This has me wondering if I shouldn't just do everything manually, like this:
Instantiate each view.
Set the DataContext of each view to the appropriate view-model.
Give the ContentControl a name and make it public.
Handle the PropertyChanged event for the window.
In the event handler, manually set the Content property of the ContentControl to the appropriate view, based the CurrentViewModel (using if statements).
This seems to work, but it also seems very inelegant. I'm hoping there's a better way.
Could you please advise me the best way to handle view switching so that value converters don't fire unnecessarily?
You should look at PRISM or any other composite UI framework. Prism will give you a great mechanism for this type of thing.
I solved this by getting rid of all IValueConverter and IMultiValueConverter classes and just using the ViewModel to provide all data. It turns out, this requires less code and hassle, and doesn't sacrifice anything that I'm aware of.

Categories