Notify ViewModel when View is rendered/instantiated - c#

I have a custom usercontrol (ChartControl) that I use within my WPF app (MainApp) and which I render as follows:
<ContentControl Grid.Row="1" Content="{Binding ChartControl, Mode=OneWay}" />
Upon starting MainApp the following are executed in the given order:
MainApp View
MainApp ViewModel
ChartControl ViewModel
ChartControl View
I instantiate the ChartControl ViewModel from within the constructor of my MainApp ViewModel. The problem is that after instantiating the ChartControl ViewModel I also need to call a method of ChartControl from within MainApp.
The problem I am having is that I need the ChartControl view to be rendered (have its InitializeComponent executed) before I call the method as part of its viewmodel.
I thought one solution could be to notify the view model from the view when it is fully instantiated and set up. Is that a viable solution and if yes how would I do that?
In summary, I need the view to be fully set up before invoking a method of the matching viewmodel. The problem I am having is that in this case the view model is instantiated first and only then is the view rendered.
Any ideas?
Thanks

You can make use of Interactivity triggers to fire Command on your VM on any UI event
You can listen to Loaded event of UserControl like below and bind it to Command on your VM:
<UserControl x:Class="Test.TestView.MyUserControl"
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"
x:Name="myControl" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ElementName=myControl, Path=OnLoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
And sure you will have Command in your VM as
public ICommand OnLoadedCommand { get; private set; }
public MyUserControl()
{
OnLoadedCommand = new DelegateCommand(OnLoaded);
}
public void OnLoaded()
{
}

Another way to hook up the Loaded event, basically rendering the same result as nit's answer, is simply referencing your viewmodel in the constructor of the view and adding an event handler which in turn calls whatever method you need to call, like this:
public MyControl()
{
InitializeComponent();
this.Loaded += (s, e) => { ((MyViewModel)DataContext).MyInitializer(); };
}
If you find the syntax confusing you might want to read up on Anonymous methods and Subscribing to event handlers (using anonymous methods).

I'm using similar solution like Hogler only with reflection (lazy coupled solution). I dont want referencing specific type of my ViewModel (because of generality, interchangeability, etc.).
public MyControl()
{
InitializeComponent();
Loaded += MyControl_Loaded;
}
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
(DataContext.GetType().GetProperty("LoadedCommand")?.
GetValue(DataContext) as ICommand)?.
Execute(null);
}
ViewModel can (dont have to) contain desired command like property (LoadedCommand in this case). Nothing more.

In an MVVM world I found that when creating a visual item and putting it onto the view (in this case adding to a list) the item wouldn't be in the visual tree until the loaded event fired.
My view model contained the items list in an observable collection which the XAML view would display.
ObservableCollection<MyControl> Items;
I'd add an item to the list, but when I perform an operation that requires it to be in the visual tree and performs visual tree recursion, this couldn't happen immediately after. Instead I had to code something like this:
var newItem = new MyControl();
newItem.Loaded += NewItemLoaded;
Items.Add(new MyControl());
The event handler would then unhook and perform the operation - and at this point is was in the visual tree as required:
private void NewItemLoaded(object sender, RoutedEventArgs e)
{
var item = sender as MyControl;
item.Loaded -= NewItemLoaded;
// now this item is in the visual tree, go ahead and do stuff ...
}

Related

In WPF and by using Prism, how to use the MouseButtonEventArgs as a parameter of the command for a window?

I want to move a borderless windows, and before I adopt the Prism framework, I'd do it as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MouseDown += Window_MouseDown;
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
DragMove();
}
}
}
but I don't know how to implement this while using Prism in MainWindowViewModel.cs (the view model), it seems the InvokeCommandAction can pass the event argument for an element like button or so, but it doesn't work for a window in my case.
Can anyone help me on this? Thanks in advance.
I don't know how to implement this while using Prism
I don't know what this is supposed to be exactly, but I'll assume it's how to call the view model when some event on the view happens:
The cleanest option is an attached behavior. Alternatively, you can use an InvokeCommandAction variant like DevExpress' EventToCommand that supports forwarding the parameter.
Well, finally I got the event fired, but it seems this approach contradict the concept of MVVM pattern, which requires that the view model should not know anything about nor has any dependency upon any view elements.
In my case, I can add the Interaction.Triggers to the Window and pass the MouseButton to the view model by usng Prism’s InvokeCommandAction, as follows:
<Window
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDown">
<prism:InvokeCommandAction Command="{Binding WindowMouseCommand}" TriggerParameterPath="ChangedButton" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the view model:
public DelegateCommand<object> WindowMouseCommand { get; private set; }
...
WindowMouseCommand = new DelegateCommand<object>(WindowMouse);
...
private void WindowMouse(object mouseButton)
{
if (mouseButton is MouseButton m)
{
if (m == MouseButton.Left)
{
// DragMove();
}
}
}
if I want to call the .DragMove(), I need a reference of the Window... it's not the correct implementation of MVVM pattern.
So what is the best approach/practice for that?
I was suddenly enlightened when I saw this answer:
https://stackoverflow.com/a/3426183/10958770
yes, moving a window is a pure UI logic, therefore it's not necessary to move it to a ViewModel... so let me just leave it in the view.

How to raise an event in a ViewModel from within a subview's ViewModel

I have an application with a main view that contains several subviews. The views have a corresponding ViewModel that display some data and monitor events contained in models.
Let's say the main view looks like this:
<UserControl x:Class="MyView" Name="myView">
<StackPanel>
<local:MySubView Name="mySubView" someProperty="{Binding DataContext.someField, ElementName=myView}"/>
[...]
</StackPanel>
</UserControl>
and the sub view looks like this:
<UserControl x:Class="MySubView"
prism:ViewModelLocator.AutoWireViewModel="True">
[...]
</UserControl>
Then in MySubViewModel.cs, some event happens and a function is called with parameter:
public Event EventHandler<SomeClass> MySubViewEvent;
[...]
void foo() {
SomeClass o = new SomeClass(...);
MySubViewEvent.Invoke(this, o);
}
Now, in MyViewModel.cs, I do not have a direct reference to the subview and thus cannot say something like
subViewModel.MySubViewEvent += OnSubViewEvent;
void OnSubViewEvent(object sender, SomeClass param) { ... }
I can add some property to MySubViewModel and make it dependent on some field in MyViewModel with a dependency property, but 1. how can I do it the other way round such that some callback or event handler will be called in MyViewModel when the event is raised in MySubViewModel, and 2. can I somehow pass event handlers as dependency properties?
Or should I use something else than DependencyProperties for this?
What is the most effective way to achieve this?
Textbook example for using the EventAggregator, though I should add that child view models are also a textbook example for going view model-first. The ViewModelLocator is most useful for independent top-level views...
That being said, in sub view model:
_eventAggregator.GetEvent<MySubViewEvent>().Publish();
Somewhere else:
public MainViewModel( IEventAggregator eventAggregator )
{
eventAggregator.GetEvent<MySubViewEvent>().Subscribe( OnSubViewEvent );
}

WPF, how do I trigger ListBox.ScrollIntoView using MVVM pattern?

I have a WPF app where the content of a ListBox is updated when the user presses a button. My initial problem was refocusing the ListBox to a specific SelectedIndex value which is binded to an ActiveItem property in my ViewModel. I was able to solve this issue with the following code:
XAML:
<ListBox ItemsSource="{Binding ListOfItems}" SelectedIndex="{Binding ActiveItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True" SelectionChanged="ListBox_SelectionChanged" x:Name="ListBoxSelector">
Code-behind:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBoxSelector.ScrollIntoView(ListBoxSelector.SelectedItem);
}
However, I currently have the above code-behind in the MainWindow.xaml.cs file instead of my ViewModel. My question is how do I move this code to the ViewModel so that I can stick to the MVVM pattern? I can't quite figure out how to properly address the ScrollIntoView property of the ListBox from the ViewModel.
You can force the selected item to scroll into view using a Behavior class.
public class perListBoxHelper : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
base.OnDetaching();
}
private static void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = sender as ListBox;
if (listBox?.SelectedItem == null)
return;
Action action = () =>
{
listBox.UpdateLayout();
if (listBox.SelectedItem != null)
listBox.ScrollIntoView(listBox.SelectedItem);
};
listBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
}
More details on my blog post.
Also, personally I'd bind to SelectedItem of the ListBox rather than SelectedIndex, and handle any processing on item selection in that property setter, rather than using an event handler.
The MVVM pattern doesn't preclude the use of code behind. In fact, it can't because there are cases where code behind is the right thing to do. A view model is a model, it is not a view replacement. Another way to think about it is that the view model contains the what and the view contains the how. At least, this is my interpretation of the MVVM pattern.
In your case, the what are the list of items and the current item. The how is the ListBox itself and its default behavior. It seems to me that scrolling the selected item into view is an additional behavior, and therefore should be kept in the view. You're not violating the MVVM pattern because you're keeping the what in the view model and the how in the view.
I'm not sure if my solution is MVVM pattern.But to such problem,it can resolve the problem.
Here is what I will do: If Button is pressed ,it will trigger a command to call method in ViewModel.When ViewModel finish it's job, viewModel throw an custom event(where include item index where listbox should scroll into). And before this happen, when View is Loaded,View's Code-behind should listen to it's ViewModel through View's DataContext, and do scrollIntoView.
As I said,I'm not sure if it's MVVM way,But I thought it's acceptable.
There isn't the universal solution for every request regarding this and as others have mentioned MVVM doesn't mean that there is no code behind but no unnecessary code behind.
However in your particular request there is the solution if you want no code behind - make a class that inherits from ListView and handles request as you would like it to be handled and then use it in your XAML.

ContentControl Content Property not changing with hosted content

I am trying to learn MVVM and have come across a weird snag. I have a main menu with a drawer control that comes out and shows a menu:
In the main window where this drawer is, I have a ContentControl where I set its content with a Binding.
<ContentControl x:Name="MainWindowContentControl" Content="{Binding Path=WindowContent}"/>
This window's binding is set to a view model.
<Window.DataContext>
<viewmodels:MainWindowViewModel/>
</Window.DataContext>
and here is the ViewModel:
MainWindowViewModel.cs
public class MainWindowViewModel: ViewModelBase
{
private object _content;
public object WindowContent
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged(nameof(WindowContent));
}
}
public ICommand SetWindowContent { get; set; }
public MainWindowViewModel()
{
SetWindowContent = new ChangeWindowContentCommand(this);
}
}
So far up to this point, everything works fine. So for example, if I click "Recovery Operations", I get this:
RecoveryOperationsView.xaml
In "RecoveryOperationsView.xaml" (which is a UserControl) I also reference the view model from above like so..
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
and have a button to call the command to change the Content property of the ContentControl from the main window..
<Button Grid.Row="2" Content="Restore Database" Width="150" Style="{StaticResource MaterialDesignFlatButton}" Command="{Binding SetWindowContent}" CommandParameter="DatabaseRecovery" >
In my class to process the commands, I change the content based off of the passed parameter using a switch statement like so
ChangeWindowContentCommand.cs
public class ChangeWindowContentCommand : ICommand
{
private MainWindowViewModel viewModel;
public ChangeWindowContentCommand(MainWindowViewModel vm)
{
this.viewModel = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
switch (parameter)
{
case "Home":
viewModel.WindowContent = new HomeView();
break;
case "RecoveryOps":
viewModel.WindowContent = new RecoveryOperationsView();
break;
case "DatabaseRecovery":
viewModel.WindowContent = new DatabaseRestoreView();
break;
}
}
}
However, this is where I get lost... If I click something within this new window, say "Restore Database" and inspect it with a breakpoint, I can see the property being changed but the actual ContentControl Content property doesnt change to the new UserControl I made... I can change the content with anything in the drawer, but if I try to click a button in the hosted Content of the ContentControl nothing changes. What am I missing?
It's hard to be 100% sure without having your project to test with, but I am fairly confident that at least one of the issues is that your UserControl and your MainWindow use different instances of the MainWindowViewModel. You do not need to instantiate the VM for the user control, as it will inherit the DataContext from the MainWindow. The way it works in WPF is that if any given UIElement does not have theDataContext assigned explicitly, it will inherit it from the first element up the logical tree that does has one assigned.
So, just delete this code, and it should solve at least that issue.
<UserControl.DataContext>
<viewmodels:MainWindowViewModel/>
</UserControl.DataContext>
And since you're learning WPF, I feel obligated to provide a couple other tips. Even though you're using a ViewModel, you are still mixing UI and logic by creating a very specific implementation of ICommand and assigning a UI element through your ViewModel. This breaks the MVVM pattern. I know MVVM takes a little time to understand, but once you do, it is very easy to use and maintain.
To solve your problem, I would suggest creating View Models for each of your user controls. Please see this answer, where I go into quite a bit of detail on the implementation.
For switching the different views, you have a couple of options. You can either use a TabControl, or if you want to use a command, you can have a single ContentControl bound to a property of MainWindowViewModel that is of type ViewModelBase. Let's call it CurrentViewModel. Then when the command fires, you assign the view model of the desired user control to that bound property. You will also need to utilize implicit data templates. The basic idea is that you create a template for each of the user control VM types, which would just contains an instance of the Views. When you assign the user control VM to the CurrentViewModel property, the binding will find those data templates and render the user control. For example:
<Window.Resources>
<DataTemplate DataType = "{x:Type viewmodels:RecoveryOperationsViewModel}">
<views:RecoveryOperationsView/>
</DataTemplate>
<!-- Now add a template for each of the views-->
</Window.Resources>
<ContentControl x:Name="MainWindowContentControl" Content="{Binding CurrentViewModel}"/>
See how this approach keeps UI and logic at an arm's length?
And lastly, consider creating a very generic implementation of ICommand to use in all your ViewModels rather than many specific implementations. I think most WPF programmers have more or less this exact RelayCommand implementation in their arsenal.

Subscribing to an event of a child view - not working

Im working on a WPF app and I am trying to use the MainWindowViewModel that holds various views as a type of messenger to pass information between views:
The following is done in a child viewmodel:
public event Action<ModelObject> NameOfEvent= delegate {};
public void Open_Command()
{
ModelObject modelObject= RandomViewModel.ImportModelObject();
NameOfEvent(modelObject); //event is triggered while running the app
}
Then in the constructor of my MainWindowViewModel (the parent of the above view model) I am subscribing to the event. And Its not picking it up
private readonly RandomViewModel _randomViewModel = new RandomViewModel();
public MainWindowViewModel()
{
Random= _randomViewModel; // sets view model to a bindable
//property that lods the view in the main window
_randomViewModel.NameOfEvent+= DoSomething; //subscribes to childs event
}
private void DoSomething(ModelObject obj)
{
//It never reaches here
}
To summarise the issue. When the event is being triggered in the child view model, the parent is not executing DoSomething method, it doesnt seem to work, i cant figure out why
Ok so my issue as seen from the comments above was that I had a double instance of my childViewModel, this was done as I employed two techniques that initialize view models:
1.) Setting Data Context in xaml of my view initializes a viewmodel
<UserControl.DataContext>
<local:MyViewModel>
</UserControl.DataContext>
2.) Using a technique I found to initialize view-models in a MainWindowViewModel:
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:MyViewModel}">
<view:MyView/>
</Window.Resources>
After you initialize in your MainWindowViewModel you assign it to a Bindable Property in the constructor and add it to xaml in a Content Control
Everything I learned so far for MVVM is to use DataContext but the 2nd method is a new way I found that works very well if you are initialising your viewmodels in a MainWindowViewModel.
I got this technique off of Brian Noyes course "MVVM in depth" on pluralsight.
So after I removed the customary :
<UserControl.DataContext>
<local:MyViewModel>
</UserControl.DataContext>
The solution worked, now my MainWindowViewModel holds all my viewmodels and can act as a messenger service similar to stuff that can be found in MVVM light e.t.c

Categories

Resources