UWP - Pass event to control (tunneling) - c#

I have a UWP question about inheriting/ passing a event to a user control from the parent view to child.
I created a user control to display text overlays (see code below). We had a parent view that would display an overlay when the window is resized (see code below). The overlay would display the dimensions of the window when this even is triggered.
I moved the overlay to a user control and now I'm trying to pass that resized event to the overlay control. The hope is that we can register more events to the overlay control so it can display more then the resize
information. However, I'm not sure the best way to do this. My first idea was inheriting from the view, so i could just listen to the event from the overlay control, but that resulted in errors.
I believe due to the fact that the parent view has a ViewModel (i also created one for the overlay, not sure if its actually needed yet).
I have been reading about a lot of possible ways to do this, but I'm not sure which would be the best way to do this. Does anyone have any insight on this issue ? I would be open to suggestions, links, or just a general answer of
what is the best way to achieve this in our project.
Parent view
User Control
Parent Event
Control class
Some information i have been reading about:
https://documentation.devexpress.com/WPF/17449/MVVM-Framework/ViewModels/ViewModel-relationships-ISupportParentViewModel
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/preview-events
https://social.msdn.microsoft.com/Forums/windows/en-US/742077f6-e875-44d1-8bc4-6e6516db9eda/passing-the-parent-control-event-to-child-controls?forum=winforms
https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/events-and-routed-events-overview
http://irisclasson.com/2013/12/10/passing-event-arguments-from-xaml-in-windows-store-apps-inputconverter-inputconverterparameter-etc/
https://learn.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service
Update
Adding the viewModel to the parent viewModel (terminal), and passing it to the control via the Datacontext did not work

As you're already using MVVM, I'd recommend going the full route utilizing "Interactivity", "Commands", and "child ViewModels". This is a commonly used patter in MVVM WPF applications, and can be applied to UWP apps as well.
Using "Interactivity" and interactions
The interactivity / behaviors library from Microsoft allows you to bind events in XAML to an ICommand in the ViewModel. You can get the managed NuGet package here.
From the official examples on GitHub, shortened:
<Button x:Name="button1" Content="Increment">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="Click" SourceObject="{Binding ElementName=button1}">
<Interactions:InvokeCommandAction Command="{Binding UpdateCountCommand}"/>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Forward command data to child ViewModel
Having this event now routed to your command in your parent ViewModel, you can now either call your overlay ViewModel and pass the info directly to it:
private readony IOverlayViewModel _overlayViewModel;
public ICommand UpdateCountCommand { get; set; }
ctor(IOverlayViewModel overlayViewModel)
{
_overlayViewModel = overlayViewModel;
UpdatedCountCommand = new MyICommandImplementation(UpdatedCountCommand_Executed);
}
private void UpdatedCountCommand_Executed(/* Add correct method signature */)
{
// If needed, retrieve data from parameter...
// Update overlay ViewModel text
_overlayViewModel.Text = ""; // Whichever text was calculated before
}
Or you use a messenger (mediator pattern) to send it to an overlay.

I was misusing the bindings. x:Bind and Binding are using different types of context. For this binding to work we would need to set the parent's element Datacontext to 'this'. x:Bind on the other hand does this implicitly.
<views:OverlayView DataContext="{x:Bind ViewModel.Overlay}"></views:OverlayView>

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.

C# WPF userControl to send data to mainWindow textBlock on buttonClick

I am trying to create a simple onscreen keypad created using buttons (currently a User-control), on those buttons i have a click event, when i click/touch a button i want the value of that button sent to a Text-block in my Main-window.
I can't/don't understand how to make the User-control (keypad) see the Text-block (in Main-window) to add in the value that i need.
I have seen solutions that use command Bindings and solutions that use the visual tree traversing but all of them are the main window accessing the user control, not the other way around.
All the examples are the other way around because that is how a UserControl is supposed to work.
A UserControl is a packaged piece of re-usable functionality. It should not know anything about the code that is using it.
Instead you should expose routed events in your UserControl for things like a when number was selected, and subscribe to them in your main window.
There are many ways to achieve what you want. If your MainWindow.xaml has a UserControl and you want to react to a change from the control in the MainWindow.xaml.cs file, then you could add a delegate to the UserControl code behind and register a handler for it in the MainWindow.xaml.cs file. Implementing new delegates are generally somewhat simpler than implementing RoutedEvents, which is another way that you could handle this situation.
Using a delegate like this will enable you to effectively pass a signal to the main view from the child UserControl code behind, which you can react to in any way you want to. Rather than explain the whole story again here, please see my answers from the Passing parameters between viewmodels and How to call functions in a main view model from other view models? posts here on Stack Overflow for full details on how to achieve this.

Calling a method from Usercontrol xaml.cs to View Model

I am using MVVM in my project and I am implementing the Diagram Designer and I am using RAD Diagram control of Telerik.I am dragging the shape from my RadToolBox to canvas for which I have implemented the event.Now on this event I want to save the diagram in RavenDB once the shape has been dropped on Canvas ( Ruler).This whole thing is in the User control stored in one library and I am creating the reference of this library in my actual WPF form which is in other library.My requirement is I want to call the Save command which is present in View Model of WPF form, from my xaml.cs of the user control and this user control is present in other library.
How shall i achieve the same.Please help.
Why do have multiple assemblies? If you don't need them, then make your life simpler and just use NameSpaces to separate your controls from your forms in the same assembly.
Right now that's said :-)
The easy way is via an Event. Add an event to your UserControl, and then in the Window add the code when the event happens.
Have a read through MSDN Events
I really dont Why you would need to call a save command from another WindowsForm.
Telerik UI Diagram provides indefined save Command which is used to save the Diagram,
InXAML,
<Grid.CommandBindings>
<CommandBinding Command="telerik:DiagramCommands.Save" Executed="ExecuteSave" />
</Grid.CommandBindings>
And In code behind define save function,
private void ExecuteSave(object sender, ExecutedRoutedEventArgs e)
{
e.Handled = true;
this.diagram.save();
}

Caliburn Micro WindowManager set WindowStyle.None and allow dragging

I'm currently very stuck with this, my designer wants to have our app with WindowStyle.None to remove the borders and default ugly controls, he has then add custom controls, usually to allow dragging in the past we have used a rectangle and monitored the mousedown event to allow for dragmove.
However with Caliburn micro we lose control of the window because windowmanager create this for us, I'm aware you can override the create window method, but this still doesn't give access to adding UI elements to the window itself and binding to those events. Or at least i can work out a way to do this.
Basically what we are trying to achieve is the "mainwindow" with a WindowStyle.None and that ability to drag and move the window. My googling has failed to give a solid answer on this, and im hoping someone here has an idea.
Caliburn.Micro doesn't force you to make the all your views UserControls. The main view or the one your showing as the main window can be a Window control and you can set properties directly on that Window such as "WindowStyle.None". When Caliburn.Micro sees that the view behind your main view model (the view model you are using as the root, then one you are creating first) is actually a Window and not a UserControl then it will honor this and show that window, It Will Not create a new Window. So you can set your properties directly on that Window and everything shall work fine.
The Caliburn.Micro WindowManager provides overrides to its Show methods that allow you to set the settings of the window that is created.
Have a look here for an example.
Alternatively, you can use a Window directly as your view type (in XAML and the generated code behind file), and set the properties declaratively in the XAML.
If you wish to enable all of your dialogs etc to have common UI components, then you could create a derived WindowManager type that delegates the call to the CM WindowManager but wraps the passed in view model with your common view model. Then register this custom window manager in the bootstrapper rather than the default CM window manager.

How to pass information from one WPF UserControl to another WPF UserControl?

I've got a WPF application.
On the left side there is a stackpanel full of buttons.
On the right side there is an empty dockpanel.
When user clicks a button, it loads the corresponding UserControl (View) into the dockpanel:
private void btnGeneralClick(object sender, System.Windows.RoutedEventArgs e)
{
PanelMainContent.Children.Clear();
Button button = (Button)e.OriginalSource;
Type type = this.GetType();
Assembly assembly = type.Assembly;
IBaseView userControl = UserControls[button.Tag.ToString()] as IBaseView;
userControl.SetDataContext();
PanelMainContent.Children.Add(userControl as UserControl);
}
This pattern works well since each UserControl is a View which has a ViewModel class which feeds it information which it gets from the Model, so the user can click from page to page and each page can carry out isolated functionality, such as editing all customers, saving to the database, etc.
Problem:
However, now, on one of these pages I want to have a ListBox with a list of Customers in it, and each customer has an "edit" button, and when that edit button is clicked, I want to fill the DockPanel with the EditSingleCustomer UserControl and pass it the Customer that it needs to edit.
I can load the EditCustomer usercontrol, but how do I pass it the customer to edit and set up its DataContext to edit that customer?
I can't pass it in the constructor since all the UserControls are already created and exist in a Dictionary in the MainWindow.xaml.cs.
so I created a PrepareUserControl method on each UserControl and pass the Customer to it and can display it with a textbox from code behind with x:Name="..." but that is not the point, I need to DataBind an ItemsControl to a ObservableCollection to take advantage of WPF's databinding functionality of course.
so I tried to bind the ListBox ItemSource in the View to its code behind like this:
<UserControl.Resources>
<local:ManageSingleCustomer x:Key="CustomersDataProvider"/>
</UserControl.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding Path=CurrentCustomersBeingEdited, Source={StaticResource CustomersDataProvider}}"
ItemTemplate="{DynamicResource allCustomersDataTemplate}"
Style="{DynamicResource allCustomersListBox}">
</ListBox>
</DockPanel>
which gets a stackoverflow error caused by an endless loop in the IntializeComponent() in that view. So I'm thinking I'm going about this in the wrong way, there must be some easier paradigm to simply pass commands from one UserControl to another UserControl in WPF (and before someone says "use WPF commanding", I already am using commanding on my UserControl that allows the user to edit all customers, which works fine, but I have to handle it in my code behind of my view (instead of in my viewmodel) since I need the parent window context to be able to load another user control when its finished saving:
<Button Style="{StaticResource formButton}"
Content="Save"
Command="local:Commands.SaveCustomer"
CommandParameter="{Binding}"/>
private void OnSave(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
Customer customer = e.Parameter as Customer;
Customer.Save(customer);
MainWindow parentShell = Window.GetWindow(this) as MainWindow;
Button btnCustomers = parentShell.FindName("btnCustomers") as Button;
btnCustomers.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
}
So how in WPF can I simply have a UserControl loaded in a DockPanel, inside that UserControl a button with a command on it that loads another UserControl and sends that UserControl a specific object to which it can bind its controls?
I can imagine I just do not know enough about WPF commands at this point, if anyone can point me in the right direction from here, that would be great, or if you think this "loading UserControls in a DockPanel pattern is foreign to WPF and should be avoided and replaced with another way to structure applications", that would be helpful news as well. You can download the current state of my application here to get an idea of how it is structured. Thanks.
I've just finished a LOB application using WPF where this sort of problem/pattern appeared constantly, so here's how I would have solved your problem:
1) In the DataTemplate where you create each item in the ListBox, along with it's edit button, bind the Button's tag property to the Customer object underlying that list box item.
2) Create a Click event handler for the button, and set the Button's Click event to fire the handler.
3) In the event handler, set the Content property of the UserControl.
4) Set up a DataTemplate in scope of the User Control (perhaps in the resources of it's immediate container) which describes an editor for that single customer.
Another approach that will work is to declare a Customer dependency property on your EditCustomer class, then set that property (perhaps through a XAML Trigger) when the button is clicked.
I hope this isn't too vague. If nothing else, know that the problem you're facing is very solvable in WPF.
This is where you use the Mediator pattern. There's several blog posts on this topic (for instance), and there's implementations of the pattern in some WPF frameworks (such as EventAggregator in Prism).
I don't have the time to really dig into this (it's an interesting question and I hope you get a good answer-- I can see myself running into a similar situation in the future).
Have you considered getting a little less WPF-y and falling back to firing an event on your source UserControl with an EventArgs that contains the customer, then in the event handler, firing the appropriate command on the target control?

Categories

Resources