WPF event handling between objects - c#

I'm pretty new to C#/WPF, coming from Obj-c background. I'm not really sure how this works in terms of object oriented design, and how different classes see each other.
So I have a large view (MainView) that has some custom plots and a datagrid on the bottom (Datagrid is in its separate xaml and has .cs file behind it). There is a custom object that gets added to the plots that when you drag it, the datagrid gets updated (by using dataGrid.ScrollIntoView). The code for the ScrollIntoView is in the xaml.cs file of the Datagrid.
To me this makes sense since the MainView has all the components and "sees" all the objects, so when the event handler of the dragWindow gets called, then the MainView asks for the DataGrid, and calls its method to update its column position. (This is the way I understand it, please feel free to correct me if I'm wrong).
Now I want to go the other way as well. So that if I update the scrollbar horizontally, then the dragwindow in the MainView will get updated. This does not make as much sense to me. I can create an event handler in the xaml.cs of the datagrid. But it does not see the dragWindow in the MainView right? So I'm not quite sure how to start implementing this functionality. Any help is always appreciated. Thanks!

Your grid control should expose an event to notify any consumers (MainView in this case) that scrolling has taken place.
public class YourGridControl
{
public event EventHandler GridScrolled;
}
MainView can then attach a handler to this event in the designer or in the code:
gridCtrl.GridScrolled += OnGridScrolled;
private void OnGridScrolled(object sender, EventArgs e)
{
// Logic here
}

Related

MVVM-way to notify neighbour usercontrol about changes

all!
In my main window I have a Grid with 2 columns. In column 0 is a usercontrol with settings, in column 1 is usercontrol with content.
The goal is to reset usercontrol with content when settings are changed. What is the right "MVVM"-way to do it?
Both usercontrols are implemented in MVVM-way, having all business logic in ViewModels.
Say I have a CheckBox bound to a Property in the settings-usercontrol:
Settings.xaml
...
<CheckBox IsChecked="{Binding Path=MySettingNr1}">
...
In Settings_ViewModel.cs
...
public bool MySettingNr1
{
get
{
return _model.SttNr1;
}
set
{
if(_model.SttNr1 == value) return;
_model.SttNr1 = value;
OnPropertyChanged(nameof(MySettingNr1));
}
}
...
How can I notify my content usercontrol if user clicks this checkbox?
Routed event would possibly not do, because both usercontrols are neighbours in the main window grid.
The only way I thought about was to fire an event in the usercontrol with settings, catch it in main windows and call a function of the usercontrol with content. Is there a way to make this call chain shorter?
Thanks in advance.
You could use a single shared view model for the window that both user controls bind to, i.e. they both inherit the DataContext of the parent window. This way they could communicate through the shared view model directly.
If you prefer to have two different view models, one for each user control, you could use an event aggregator or a messenger to send an event/message from one view model to antoher in a loosely coupled way.
Most MVVM libraries have implemented solutions for this. Prism uses an event aggregator and MvvmLight uses a messenger, for example.
Please refer to this blog post for more information about the concept.

Does calling View Model methods in Code Behind events break the MVVM?

I wonder if that would break the MVVM pattern and, if so, why and why is it so bad?
WPF:
<Button Click="Button_Click" />
Code Behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.CallMethod();
}
View Model:
public void CallMethod()
{
// Some code
}
IMHO, it keeps the code behind quite simple, the view model is still agnostic about the view and code behind and a change to the view doesn't affect the business logic.
It seems to me more simple and clear than Commands or CallMethodAction.
I don't want the kind of answer "it is not how it should be done". I need a proper and logical reason of why doing so could lead to maintenance or comprehension problems.
Nope, this is perfectly fine.
It's the View's job to handle user input and interact with the ViewModel. A button click event-handler, which calls a method of the ViewModel in response, falls quite cleanly into this role.
What you have posted is clean, readable, efficient, maintainable, and fully in the spirit of the MVVM design pattern.
Now, in a more general sense, what you really want to ask is: "why choose ICommands, vs Event Handlers for MVVM?" Well, you certainly wouldn't be | the | first.
No, it doesn't break MVVM, as long as you don't introduce logic that more appropriately belongs in the viewmodel.
It has the potential to reduce the clarity, IMO, because it breaks the view into XAML and c# files that are tightly coupled and where you can't see everything going on in one place. I find it easier to have zero code behind because it means less context switching when working on the view.
It can also make it more challenging to work in an environment where your UI designer isn't a C# programmer, because then two different people are maintaining the tightly-coupled files.
Edit
Here's an example of what I mean. This is from a weekend project I did to implement Minesweeper in WPF for fun and experience. One of my biggest WPF challenges was mouse input. For anyone who hasn't wasted time on the game before, the left mouse click reveals a cell, the right mouse button toggles a flag on the cell, and the middle mouse button will (conditionally) reveal adjacent cells.
I first started by considering using System.Windows.Interactivity's EventTrigger along with InvokeCommandAction to map events to commands. This sort-of worked for the right mouse button (wasn't a true click event, but a MouseRightButtonUp) but it wouldn't work at all for the middle mouse button which had no specific actions, only the generic MouseDown/MouseUp. I briefly considered prism's variation on InvokeCommandAction which could pass the MouseButtonEventArgs to its handler, but that very much broke the MVVM concept and I quickly discarded it.
I didn't like the idea of directly putting event handlers in the code-behind because that tightly coupled the action (the mouse click) and the response (revealing cells, etc.). It also wasn't a very reusable solution - every time I wanted to handle a middle click I'd be copying, pasting, and editing.
What I settled on was this:
<i:Interaction.Triggers>
<mu:MouseTrigger MouseButton="Middle" MouseAction="Click">
<i:InvokeCommandAction Command="{Binding Path=RevealAdjacentCells}" />
</mu:MouseTrigger>
</i:Interaction.Triggers>
In this case, there's no code in the code behind. I moved it into a MouseTrigger class I created that inherits from System.Windows.Interactivity.TriggerBase which, while being view-layer code, isn't part of any specific view, but a class which any view could utilize. This handler code is as agnostic as possible as to what kind of element it's attached to - anything derived from UIElement will work.
By leveraging this approach, I gained two key things over doing this in the event handlers on the code-behind:
There's a loose coupling between the event and the action. If I had a UXD working on the UI, they could change what mouse button the command was associated to by just editing a line of XAML. For example, swapping right and middle mouse buttons is trivial and requires no .cs changes.
It's reusable on any UIElement, not tied to any particular one. I can pull this out anytime I need to solve this kind of problem in the future.
The main drawback here is that it was initially more work to set up. My MouseTrigger class is more complex than the event handlers by themselves would be (mainly around properly handling dependency properties & changes thereof). XAML can also often be rather verbose for something that would seem simple.

MVVM: Bring control into view

I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!

Correct UserControl Usage?

I just started breaking up my GUI application into UserControls. I have a TabControl with a bunch of TagePages. Obviously my MainForm.cs file was filled up with tons of events and controls etc and it got very messy quick.
So a previous question gained me the insight of how to create a UserControl. I intend on creating a UserControl for each TabPage and I was wondering how I can interact with Components on the main form or other UserControls.
Here is an example of a TabPage that I have made using a UserControl, which needs to Enable or Disable a button depending which TabPage is currently selected. Is this proper usage or is there a better way?
public partial class TabDetails : UserControl
{
private RequestForm fRequestForm;
public TabDetails()
{
InitializeComponent();
}
public void CustomInitialization(RequestForm pRequestForm)
{
fRequestForm = pRequestForm;
pRequestForm.TabControl_Main.SelectedIndexChanged += SelectedTabIndexChanged;
}
private void SelectedTabIndexChanged(object pSender, EventArgs pEvents)
{
fRequestForm.Button_SubmitRequest.Enabled = fRequestForm.TabControl_Main.SelectedTab != fRequestForm.Tab_Details;
}
}
In the MainForm.cs constructor I call:
this.tab_Details1.CustomInitialization(this);
This doesn't look like a good use of a user control. The user control should not decide how things in the form should behave when something is changed in the user control. A user control should be unaware of its container and should operate in any container.
The user control should notify the form that something has changed without telling what's the internal implementation and the form should decide what to do.
Example:
A user control named "NameUserControl" consists of TitleComboBox, FirstNameTextBox and LastNameTextBox. The user control wants to notify when one of the values has changed.
Wrong Way:
Create events:
TitleComboBox - SelectedIndexChanged.
FirstNameTextBox, LastNameTextBox - TextChanged.
The problems here:
You expose the internal controls behavior. What will happen if you want to change the TitleComboBox to TextBox? You'll have to change the event name and implementation.
You expose the fact that you use exactly 3 different controls. What will happen if you want to use the same text box for first and last name? You'll have to delete one event and change the name of the other.
Good Way:
Create only a single event: NameChanged and expose 1 property of FullName or three different properties for the values.
Either way the form subscribe to the event and decide what to do next.
Another thing to think about: the more you add more functionality to your user control, you either make it less reusable or you make its code more complex. For example, if you add validation inside the user control, you'll find one day that you need it without validation, so you'll add a property "bool ValidateData" or it will be so complicated that you'll need to build another control. One way to solve that is to build very small user controls, but combine them in one or more bigger user controls that fit all your current needs.

AutoCompleteComboBox Arrow Up/Arrow Down keys to scroll list

I created a simple AutoCompleteBox in my WPF app and it loads great with code intercepting the Populate event, but when the list pops up and I hit the arrow down key and get to the end of the list the vertical scroll bar doesn't scroll.
The values keep changing in the field like it is scrolling through them, but the scroll bar doesn't move.
If I use the mouse it scrolls fine.
I just need the arrow key to scroll it.
Any ideas/suggestions?
I am new to WPF and have searched forever for this fix.
Attach a SelectionChanged event and then, inside the handler:
private void AutoCompleteBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
AutoCompleteBox box = (AutoCompleteBox)sender;
ListBox innerListBox = (ListBox) box.Template.FindName("Selector", box);
innerListBox.ScrollIntoView(innerListBox.SelectedItem);
}
I see the same behavior. I found a post on codeplex talking about a different issue but at the bottom of the post they have a class AutoCompleteBoxEx that supports ScrollIntoView, so you can hook up the SelectionChanged even and this should get you the behavior you want. I have no idea why this is not baked in. I have had a chance to test out the posted code.
Update
Just pasted the code from the post into a class and used it in the XAML by changing AutoCompleteBox to AutoCompleteBoxEx and adding namespace for AutoCompleteBoxEx and it worked fine. You don't have to specify any event in the XAML, nor do you need to add any code to the code behind.

Categories