Propagate an event from style to MainWindow in WPF - c#

I have a custom style in a separate XAML CustomTabItem.xaml which raises an event like:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="myProject.CustomTabItem">
...
...
<MenuItem Header="One">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<EventSetter Event="Click" Handler="ClickNewSpaceOne"/>
</Style>
</MenuItem.Style>
</MenuItem>
...
...
</ResourceDictionary>
And this easily raises an event in a file I created called CustomTabItem.xaml.cs:
namespace myProject
{
partial class CustomTabItem
{
private void ClickNewSpaceOne(object sender, RoutedEventArgs e)
{
//do stuff here
}
}
}
This all works fine, but I now need to raise an event in the MainWindow (of course in the event handler ClickNewSpaceOne) but I cannot figure out how to propagate this event to the MainWindow.
I found this article but it doesn't really look like the same situation, so any different article that I didn't find or any answer I would really appreciate.

The practice of using EventSetter in this case, not the best. And here's why:
He is bound to the BAML file that and should be event handler
Because it, is limited to the global function of the event, he just looking event handler in xaml.cs file. Also, because of this, from MSDN:
Event setters cannot be used in a style that is contained in a theme resource dictionary.
EventSetter can not be set in the Trigger
Quote from link:
Because using EventSetter to wire up event handler is a compile-time feature which is plumbed through IStyleConnector interface, there is another interface called IComponentConnector which is used by the XAML compiler to wire up event handler for standalone XAML elements.
What alternatives?
1 - Attached dependency property
Use the attached dependency property and its UIPropertyMetadata, you implement the necessary logic. For example:
// GetValue
// SetValue
public static readonly DependencyProperty SampleProperty =
DependencyProperty.RegisterAttached("Sample",
typeof(bool),
typeof(SampleClass),
new UIPropertyMetadata(false, OnSample));
private static void OnSample(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue) == true)
{
// do something...
}
}
More information can be found here:
How to inherit Button behaviour in WPF style?
Quit application from a user Control
How to clear the contents of a PasswordBox when login fails without databinding?
2 - Commands
Command in the WPF is a very powerful. Quote from MSDN:
The first purpose is to separate the semantics and the object that invokes a command from the logic that executes the command. This allows for multiple and disparate sources to invoke the same command logic, and it allows the command logic to be customized for different targets.
In this case, they can and should be used in Styles, Templates, DataTemplates. In style, you can set a command like this:
<Setter Property="Command"
Value="{Binding DataContext.YourCommand,
RelativeSource={Relative Source AncestorType={x:Type Control}}}">
Also, if you want to reference the command, you can declare the command as a static property then you can use Static extention to reference it.
3 - Using EventTrigger with Interactivity
In this case, the command is called by the EventTrigger. For example:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter" >
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
More information, can be founded here:
Using EventTrigger in XAML for MVVM
Binding WPF events to MVVM Viewmodel commands

Related

CaliburnMicro IsVisibleChanged does not fire

The event IsVisibleChanged couldn't be routed to the ViewModel. What could be the cause?
If I'm testing the event as normal WPF event (no Caliburn Message.Atach) with CodeBehind, the Event is fired as expected. If I'm testing the Caliburn Message.Atach with other events of the UserControl like LayoutUpdated, they work like expected with the ViewModel. But I'm not able to get IsVisibleChanged fired to my ViewModel.
View
<UserControl x:Class="MySetupDeviceConfig.Views.SetupDeviceConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cal="http://www.caliburnproject.org"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Visibility="{Binding Visibility}"
d:DesignHeight="450" d:DesignWidth="800"
cal:Message.Attach="[Event IsVisibleChanged] = [Action UcIsVisibleChanged];">
<Grid>
...
ViewModel
public class SetupDeviceConfigViewModel : Screen
{
private Visibility _Visibility;
private ILogger Log { get; set; }
public Visibility Visibility { get => _Visibility; set { _Visibility = value; NotifyOfPropertyChange(); } }
// ...
public void UcIsVisibleChanged()
{
Log.LogInformation("IsVisibleChanged");
}
Tested with Caliburn.Micro v4.0.62-alpha and CaliburnMicro v3.2.0
Changing e.g. to the Loaded event in the view with same action/function mapping -> it works. So there is no type mismatch...
cal:Message.Attach="[Event Loaded] = [Action UcIsVisibleChanged];">
The reason for this not working is that IsVisibleChanged is a CLR event and not a routed event. As stated in the documentation.
Caliburn.Micro's Message system works on routed event not CLR events. Since Caliburn.Micro uses EventTrigger internally.
Why not just make a binding to IsVisible?
Doesn't your property need to be called IsVisible rather than Visibility? Or, change the call to NotifyOfPropertyChange from the default to NotifyOfPropertyChange("IsVisible").
Try placing the action on the first element (the Grid) inside your UserControl rather than on the UserControl itself. I tried it myself and it seemed to work on the Grid, but not on the UserControl itself, could be a Caliburn bug?
I noticed also that when I toggled the Visibility of the Grid in code-behind, the event didn't get fired, but when I bound the Visibility dependency property to a property in my ViewModel, it worked! Seems like another bug in Caliburn.
I think it's usually good practice to place events and bindings on controls inside the UserControl rather than the UserControl itself. If the UserControl gets hidden from the outside, the Grid inside it shoud fire the visibility event anyway, so it practically makes no difference.

How do I ensure that onApplyTemplate gets called before anything else

I have a wpf Custom Control on which I have been working. It has a shared New like this:
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
If an Items source has been set for the custom control this shared sub then invokes the overrideMetadata for the itemssource (as shown below)
Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
vdn.MyBaseCollection.MoveCurrentToFirst
vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
If Not IsNothing(vdn.FindButton) Then
If vdn.FindButton.Visibility = Visibility.Visible Then
vdn.RecordIndexTextBox.IsReadOnly = False
Else
vdn.RecordIndexTextBox.IsReadOnly = True
End If
End If
vdn.ResetTheNavigationButtons
vdn.SetupInitialStatesForNonNavigationButtons
End Sub
This then fails because buttons referred to in the code (and routines called from it) have not yet been instantiated because the override for OnApplyTemplate (shown below) has not been called.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
LastButton = CType(GetTemplateChild(LastButtonPart), Button)
AddButton = CType(GetTemplateChild(AddButtonPart), Button)
CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
EditButton = CType(GetTemplateChild(EditButtonPart), button)
CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub
If I add something along the lines of:
vdn.OnApplyTemplate
to OnItemsSourceHasChanged, OnApplyTemplate is called but nothing is resolved (see illustration below).
BUT if I don't set an itemssource on my control, then OnApplyTemplate gets called and the items resolve (see below)
Has anyone encountered this sort of behaviour before and found a way to correct it such that OnApplyTemplate is always the first thing to get called before anything that might require access to controls that have yet to be resolved.
Edit
The curious thing about this issue is that (and doesn't this always seem to be the case!) this was working until obviously I did something or set some property. What I am left with is a project that runs if I do not set an Items source on my custom control, and one which doesn't if I do because the custom handler I have in place to handle when the items source is changed on my custom control is running before OnApplyTemplate gets called.
Well I have at last been able to determine that my custom controls Itemssource property is being changed before the control is being drawn and rendered and therefore the code I have in place to set things up following the ItemsSource change raises null reference exceptions because the main control has yet to be rendered.
Given that it did work it must be something I've done but I'm now out od ideas as to how to delve into this further and actually find the reason. I'd welcome any suggestions you might have or potential work rounds.
Edit in relation to comments below: typical part of control template.
<!-- First Button -->
<Button Style="{StaticResource vtlNavButtonStyle}"
x:Name="PART_FirstButton"
Tag="First_Button"
Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
ToolTipService.ShowOnDisabled="False"
ToolTipService.ShowDuration="3000"
ToolTipService.InitialShowDelay="500">
<Button.ToolTip>
<Binding Path="FirstButtonToolTip"
RelativeSource="{RelativeSource TemplatedParent}"
TargetNullValue="{x:Static p:Resources.FirstText}">
</Binding>
</Button.ToolTip>
<StackPanel>
<Image Style="{StaticResource vtlImageStyle}">
<Image.Source>
<Binding Path="FirstImage"
RelativeSource="{RelativeSource TemplatedParent}">
<Binding.TargetNullValue>
<ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
</Binding.TargetNullValue>
</Binding>
</Image.Source>
</Image>
</StackPanel>
</Button>
Calling OnApplyTemplate yourself isn't going to help; the framework will call it when the template has actually been applied. That said, the order in which things happen is not deterministic -- the template may or may not be applied before the ItemsSource is set. I'm working with UWP apps for Windows 10, which is a slightly different beast, but we've solved a similar issue doing something like this:
private TextBlock textBlock;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Grab the template controls, e.g.:
textBlock = GetTemplateChild("MyTextBlock") as TextBlock;
InitializeDataContext();
DataContextChanged += (sender, args) => InitializeDataContext();
}
private void InitializeDataContext()
{
ViewModel ViewModel = DataContext as ViewModel;
if (viewModel != null)
{
// Here we know that both conditions are satisfied
textBlock.Text = ViewModel.Name;
}
}
The key is to not start listening for DataContextChanged until the template has been applied. If the data context has already been set, the first call to initializeDataContext takes care of things; if not, the callback takes care of things.
(In your case, replace our data context listening with items source listening, I suppose.)
This isn't an answer to your question, but instead expands on some things you mentioned in the comments.
I really think that it would benefit you to look into WPF commands as they pertain to custom controls. Your data navigator control sounds like it essentially supports a number of actions (go to first/previous/next/last; add; edit; cancel; etc) that you invoke using Button controls in the control template. Rather than looking for the buttons in OnApplyTemplate (at which point you store references to them so that you can presumably hook into their Click event later) you should support commands in your control: the buttons in the template would then bind to these commands.
An example would probably make this a bit clearer. The following is code for a custom control that supports two actions: go-to-first-page, and go-to-last-page. In the static constructor I register two command bindings, one for each action. These work by calling into a helper method that takes the command to "bind" to, plus a pair of delegates that get called when the action is invoked.
The commands I am using here are provided by the WPF framework, and are static properties contained in the static NavigationCommands class. (There are a bunch of other similar classes containing commands, just follow the links in the "See Also" section of that MSDN page).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace StackOverflow
{
public class TestControl : Control
{
static TestControl()
{
RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
x => x.GoToFirstPage());
RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
x => x.GoToLastPage(), x => x.CanGoToLastPage());
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
new FrameworkPropertyMetadata(typeof(TestControl)));
}
void GoToFirstPage()
{
Console.WriteLine("first page");
}
void GoToLastPage()
{
Console.WriteLine("last page");
}
bool CanGoToLastPage()
{
return true; // Would put your own logic here obviously
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute) where TControl : class
{
RegisterCommandBinding<TControl>(command, execute, target => true);
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
where TControl : class
{
var commandBinding = new CommandBinding(command,
(target, e) => execute((TControl) target),
(target, e) => e.CanExecute = canExecute((TControl) target));
CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
}
}
}
The following is the control's default template. As you can see there are simply two Button controls, each one of which binds to the relevant command via its Command property (note this is not a data binding, ie. you're not using the {Binding} markup extension).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<StackPanel Orientation="Horizontal">
<Button Command="NavigationCommands.FirstPage" Content="First" />
<Button Command="NavigationCommands.LastPage" Content="Last" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, here's the custom control in a Window. As you click the "First" and "Last" buttons you can see the actions being invoked by watching the relevant text appear in the debug console window.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<local:TestControl VerticalAlignment="Top" />
</Window>
If you use commands in this way then you should be able to simplify your control's code significantly.
I had a similar issue - a custom control (specifically, a class derived from Control) would show binding errors whenever a new instance of the control was instantiated. This was because the control template was being created before the bindings were setup. Once the bindings took effect, then the control would start to work.
To "fix" this (or work around it anyway) I just added a call to ApplyTemplate() to the control's constructor. So it ends up looking like this:
public CustomControl()
{
InitializeComponent();
ApplyTemplate();
}
Then there were no more binding errors.

Correctly handling document-close and tool-hide in a WPF app with AvalonDock+Caliburn Micro

I'm trying to use both AvalonDock 2.0 (MVVM-compliant) and Caliburn Micro in my WPF application. All works fine, except for a couple of issues connected with closing document panes or hiding tool panes.
My main viewmodel derives from Conductor<IScreen>.Collection.OneActive and exposes two BindableCollection's of Screen-derived viewmodels for Tools and Documents; the corresponding relevant XAML is like:
<xcad:DockingManager Grid.Row="1"
AnchorablesSource="{Binding Path=Tools}"
DocumentsSource="{Binding Path=Documents}"
ActiveContent="{Binding Path=ActiveItem, Mode=TwoWay}">
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.DisplayName}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
<xcad:DockingManager.LayoutItemTemplateSelector>
<views:AutobinderTemplateSelector>
<views:AutobinderTemplateSelector.Template>
<DataTemplate>
<ContentControl cal:View.Model="{Binding . }" IsTabStop="False" />
</DataTemplate>
</views:AutobinderTemplateSelector.Template>
</views:AutobinderTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Horizontal">
<xcad:LayoutAnchorablePane DockHeight="150" DockMinWidth="200">
</xcad:LayoutAnchorablePane>
<xcad:LayoutDocumentPane/>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
The template selector is as simple as that:
public class AutobinderTemplateSelector : DataTemplateSelector
{
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return Template;
}
}
1. Closing Documents
The first issue comes when handling document-pane close. AD has its document handling mechanism, which should be synchronized with CM's one. CM is based on a screen conductor; when a screen needs to be closed, the method TryClose is used to close it if possible (i.e. unless a guard method tells the framework that the screen cannot be closed, e.g. because the document is dirty). To let AD play with CM I'm using a workaround similar to that described in Prevent document from closing in DockingManager, where the main view code directly calls this method handling the docking manager closing event: when AD is closing the document, call the underlying VM guard method and cancel if required; if not cancelled, then AD goes on closing thus firing the DocumentClosed event.
To see if this could work, I first created a public TryClose method in the document viewmodel base, essentially duplicating the code in the CM TryClose override, like (IsDirty is a protected virtual method overridden by descendant viewmodels):
public bool CanClose()
{
if (!IsDirty()) return true;
MessageBoxAction prompt = new MessageBoxAction
{ ...prompt message here... };
bool bResult = true;
prompt.Completed += (sender, args) =>
{
bResult = prompt.Result == MessageBoxResult.Yes;
};
prompt.Execute(null);
return bResult;
}
This is the method called by the main view code behind in the handlers for AD document closing and document closed:
private void OnDocumentClosing(object sender, DocumentClosingEventArgs e)
{
DocumentBase doc = e.Document.Content as DocumentBase;
if (doc == null) return;
e.Cancel = !doc.CanClose();
}
private void OnDocumentClosed(object sender, DocumentClosedEventArgs e)
{
DocumentBase editor = e.Document.Content as DocumentBase;
if (doc != null) doc.TryClose();
}
Note that I cannot directly call TryClose in OnDocumentClosing, as this would cause null object reference errors in AD. This is really ugly but it works. I can now close documents and my guard methods are called appropriately before proceeding. Anyway, it would be nice to get suggestions for a less hacky solution here.
2. Hiding Tools
Another issue arises from hiding the tools panes. In this case, AD should just hide them. The AD control visibility can be bound to an IsVisible boolean property in my viewmodels implementing tool panes, using a BooleanToVisibility converter. To this end I just add the binding in the XAML:
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
...
<Setter Property="Visibility"
Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityCvt}}"/>
...
Now, if I hide a tool pane by clicking on its X button, I can see that my VM IsVisible property is set to false as expected, and the pane is hidden. Then, if I programmatically set this property back to true the pane is not shown. Even restoring the layout does not work: I can see that when the application starts and the object corresponding to the hidden VM is being added to the Tools collection its IsVisible is already false. To have it back, I must set this to true and then restore the layout. If I miss any of the two steps, the pane remains hidden. Clearly I'm not following the intended implementation strategy here. Could anyone point me in the right direction?

Access Window ViewModel from UserControl

I have a Window with a ContentControl inside. I want to show multiple view-filling UserControls like a wizard with multiple steps. Those UserControls need their own ViewModel and the possibility to replace themselves with another UserControl in the Window's ContentControl.
I want to work with the MVVM pattern and am currently struggling how to access the Window's ViewModel from the ViewModel of the UserControl.
Here is the simplified code I have so far. The content changing works without any problem when I change it inside the main ViewModel:
Window XAML:
<Grid>
<ContentControl Content="{Binding CurrentView}" />
</Grid>
Window ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private object currentView;
public object CurrentView
{
get { return currentView; }
private set
{
currentView = value;
OnPropertyChanged(); // <- Property name is set automatically, so no parameter needed
}
}
public MainWindowViewModel()
{
this.CurrentView = new UserControl1(); // Initial view to show within the ContentControl
}
}
UserControl1 XAML:
<UserControl>
<Grid>
<Button Command="{Binding SwitchToUserControl2}">Switch content</Button>
</Grid>
</UserControl>
Now I have the following "thinking problems":
If I set the DataContext of the UserControl to its ViewModel, I cannot access the MainWindowViewModel to change the CurrentView Property to UserControl2.
If I don't set the DataContext of the UserControl, I automatically inherit the correct ViewModel for Binding the Command to change the Content but haven't instantiated the ViewModel of the UserControl. I need this because many actions of the UserControl should be handled within it's own ViewModel.
In my understanding it is neccessary to have access to both ViewModels from the view but have no clue how to achieve this.
I would not have the MainWindowViewModel create a view, but rather create your first ViewModel. The ViewModel could then use events or any other mechanism to notify that it should transition to the next step.
The View portion can be handled easily in that case via DataTemplates that map the ViewModel to the appropriate View. The advantage here is that the ViewModel never knows about the View used to render it, which stays "pure" in an MVVM perspective. Right now, your ViewModel is manipulating the View layer, which is an MVVM violation.
Reed's answer is correct, and is one way to solve your problem, create the ViewModel of the control in the MainWindow, hook up the events and bind the ViewModel to the user control via a DependencyProperty.
To allow the binding of the ViewModel to work, make sure you do not set the DataContext in the Constructor of the UserControl or on the Root element of the Xaml of the UserControl. Instead, set the DataContext on the first content element of the UserControl. This will allow external bindings to the UserControl to continue working while the DataContext of the UserControl is what you want.
<UserControl x:Class="StackOverflow._20914503.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:this="clr-namespace:StackOverflow._20914503"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
</Grid>
</UserControl>
With regards to swapping controls in and out, Reed is again correct and DataTemplates are the way to go.
Another way to solve your communications problems is to use RoutedEvents. Create a RoutedEvent with in the application and since the event will have no real association to a ui element, lets create a class to publish the routed event.
public static class EventManagement
{
public static readonly RoutedEvent ChangeViewEvent = EventManager.RegisterRoutedEvent("ChangeView", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UserControl));
}
Now, in each of the UserControls (and it must be done within the code behind of a UserControl), you can call RaiseEvent which is implemented in the UIElement class. In the following code, I am picking up an event from the ViewModel of my UserControl and firing the RoutedEvent
private void ViewModel_ChangeEvent(object sender, EventArgs e)
{
RaiseEvent(new RoutedEventArgs(EventManagement.ChangeViewEvent));
}
In my main window, without know where the RoutedEvent is going to be fired from, I can add a handler to the Routed event like so
public MainWindow()
{
InitializeComponent();
this.AddHandler(EventManagement.ChangeViewEvent, new RoutedEventHandler(SomeControl_ChangeView));
}
private void SomeControl_ChangeView(object sender, RoutedEventArgs routedEventArgs)
{
}
.Net will handle the routing of the event for you based on the RoutedEvent registration.
The advantage of this approach is the separation the functionality. Everything works without knowing anything else. You can use triggers to insert UserControl into the MainWindow, they can all Raise the same RoutedEvent, and the MainWindow will handle them all.
To Summarise the flow of control. The ViewModel of the UserControl raises a standard CLR event that the UserControl handles. The UserControl Raises the RoutedEvent. .Net Bubbles the event up to the main window. The main window receives the event via its handler.
A couple of points to note.
1. The default routing strategy for RoutedEvents is Bubbling (from lowest element, say a button, to the highest, say MainWindow).
1. An event will stop once a handler has flagged the event as Handled.
1. Routing is mostly done via the Visual Tree.
If necessary, I can post the component parts of my example.
I hope this helps.

How to work with template member properties

Before anything else, I do apologize for the following verbose question. Since I'm new to WPF, I decided to explain more in order to probably get more tips!
I have a UserControl like:
<UserControl x:Class="MyNamespace.MyUserControl2"...
xmlns:local="clr-namespace:MyNamespace"
Style="{DynamicResource ResourceKey=style1}">
<UserControl.Resources>
<Style x:Key="style1" TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
...
<local:MyUserControl1 x:Name="myUserControl1" .../>
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
To access to the myUserControl1 from the code behind, I used a property.
private MyUserControl1 _myUserControl1;
private MyUserControl1 myUserControl1
{
get
{
if (_myUserControl1 == null)
_myUserControl1 = this.Template.FindName("myUserControl1", this) as MyUserControl1;
return _myUserControl1;
}
}
(Is this a good approach to access a template member?)
On the other hand, there is a dependency property in MyUserControl2 class (say DP1) that is responsible for modifying one of myUserControl1 dependency properties. (Say SomeProperty)
private static void IsDP1PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as MyUserControl2;
if (instance != null)
{
instance.myUserControl1.SomeProperty = function(e.NewValue);
}
}
When I tried to run the above code, I noticed that instance.myUserControl1 is null. So I treated it like so:
if (instance != null && instance.myUserControl1 != null)
{
instance.myUserControl1.SomeProperty = function(e.NewValue);
}
Although this approach solved the issue, it causes myUserControl1.SomeProperty to remain uninitialized. So, I put the following code snippet at the loaded event to resolve it:
private void MyUserControl2_Loaded(object sender, RoutedEventArgs e)
{
this.myUserControl1.SomeProperty = function(DP1);
}
After that, I encountered another problem!
When I set some value to DP1 using setter attribute of a style, I received a null reference exception that says myUserControl1 property is still null at the loaded event. How can I work around it? -Thanks.
I guess you haven't still a clear idea about WPF.
You are hitting so many troubles because your approach is much "winforms-like", than functional. WPF makes your life harder if you insist using it in a imperative manner.
First off, the template represents a function to instructs the WPF engine on how to create the actual visual tree during the run time. You should use the name as reference within a template ONLY inside the hosting control (i.e. MyUserControl2), and getting the instance reference from within the OnApplyTemplate method. Nowhere else.
Example:
private MyUserControl1 _myUserControl1;
public override void OnApplyTemplate()
{
this._myUserControl1 = this.GetTemplateChild("myUserControl1") as MyUserControl1;
//here you should check whether the instance is actually set
}
The reference of any control is hosted should kept as private: no protected/internal/public exposition of any of the hosted controls.
Second point: how to bind two properties together.
Your goal is to "bind" a property of a control with another exposed by the hosted one. This task is absolutely normal, and it is one of the best features offered by WPF.
Supposing that the two properties share the same type, thus can be bound directly. Within your xaml:
<ControlTemplate TargetType="{x:Type UserControl}">
...
<local:MyUserControl1 x:Name="myUserControl1"
SomeProperty="{Binding Path=DP1, RelativeSource={RelativeSource Mode=TemplatedParent}}"
.../>
...
</ControlTemplate>
Note that: (1) SomeProperty must be a DependencyProperty, (2) must be writable, (3) DP1 must be also a DP, or -at least- notify any change via the INotifyPropertyChanged pattern.
The syntax depicted binds normally: SomeProperty = DP1, but not vice versa. If you need an bidirectional mapping, you should add "Mode=TwoWay" inside the "Binding" clause.
If you want to customize the function that maps the two properties, simply define your own converter via the IValueConverter interface, then declare it in the xaml.
Here you will find several useful info: http://msdn.microsoft.com/en-us/library/ms752347.aspx
Cheers

Categories