I'm currently experimenting with C# WPF custom routed events i got stuck at a problem.
This is what i want to do: I want to fire a custom routed event from my main window which tunnels through a stackpanel to a custom control derived by the Button class. The custom control then handles the routed event.
My problem is when i fire the event the handler is never been called.
My code:
public partial class MainWindow : Window
{
public static readonly RoutedEvent MyRoutedEvent = EventManager.RegisterRoutedEvent("MyRoutedEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(UIElement));
public static void AddMyRoutedEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.AddHandler(MainWindow.MyRoutedEvent, handler);
}
}
public static void RemoveMyRoutedEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
{
uie.RemoveHandler(MainWindow.MyRoutedEvent, handler);
}
}
public MainWindow()
{
InitializeComponent();
}
private void keyClassButton1_MyRoutedEvent(object sender, RoutedEventArgs e)
{
Console.Write("\nMyRoutedEvent!");
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(MyRoutedEvent, this);
RaiseEvent(newEventArgs);
}
}
XAML code:
<Window x:Class="RoutedEvent_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedEvent_Test"
Title="MainWindow" Height="350" Width="525" MouseDown="Window_MouseDown">
<Grid>
<StackPanel Name="stackPanel1">
<local:KeyClass x:Name="keyClass1" Content="key class button" Height="30" local:MainWindow.MyRoutedEvent="keyClassButton1_MyRoutedEvent"></local:KeyClass>
</StackPanel>
</Grid>
</Window>
ok i figured it out by myself:
Although i've read it like a thousand times it clearly states in the MSDN description:
Tunneling: Initially, event handlers at the element tree root are
invoked. The routed event then travels a route through successive
child elements along the route, towards the node element that is the
routed event source (the element that raised the routed event).
[...]
My first idea of a tunneled routed event was: I fire a event from the main window and it goes through the stackpanel to the button element.
BUT INSTEAD:
You have to fire it from the button already - then it begins at the root element (main window) and goes through the control layers to the button element which fired the event in the first place.
What i did was: I fired the event from the main window so it couldn't go anywhere else
This registration doesn't seem to be correct:
public static readonly RoutedEvent MyRoutedEvent = EventManager.RegisterRoutedEvent("MyRoutedEvent", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(UIElement));
You need to add the public event ReoutedEventHandler MyRoutedEvent in the class as you registered here. This should be non static class instance level handler. I don't see it on your code.
You need need something like this on the MainWindow:
public event RoutedEventHandler MyRoutedEvent;
See MSDN example here.
Related
I am creating a wpf application and in that application there is one window with a grid in it, and in this grid I host my user controls by adding them to the grid,
I have to explain this in detail to show it’s working
so basically there is a side menu, which In actuality is a listview, and when a listview item is selected or that event is raised, the corresponding user control to that listview item is added to the grid
Well this worked, but this was slow, so instead what I did was load all the user controls to the grid and controlled the visibility property of the user control corresponding to its listview item selected
This is the code that I am using
//Adds usercontrol to grid
private void Window_Loaded(object sender, RoutedEventArgs e)
{
UserControl usc = null;
usc = new Home();
usc.Tag = "Home";
LoadGrid.Children.Add(usc)
ShowUserControl("Home");
}
//controls UserControl visibility
private void ShowUserContro(string v)
{
foreach (UIElement item in LoadGrid.Children)
{
if (item is UserControl)
{
UserControl x = (UserControl)item;
if (x.Tag != null)
{
if (x.Tag.ToString().ToUpper() == v.ToUpper())
{
x.Visibility = Visibility.Visible;
}
else
{
x.Visibility = Visibility.Collapsed;
}
}
}
}
}
//controls listview selection changed event
private void ListViewMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ShowUserContro((((ListViewItem)((ListView)sender).SelectedItem).Name));
}
My question is I want the grid to host a user control but it should be tied to a button inside the user control
Example :
(The main window where the grid is present, children's are added to the grid, and its visibility property which is collapsed and visible, this event is tied up to the listview menu)
when lets say a listview item called home is selected, the user control corrosponding to home is added and made visible, this has a button it is supposed to show another user control (that hosts a few textbox and data-grid)
You can send the main window as an input parameter to the
user-controller Constructors and in the main window put the method
of adding the user-controller and add or manage the user-controller
there.
The next solution is to give an event to the button inside the
current control and use that event in the main window, and of course
this method is better.
MainWindow.cs
public MainWindow()
{
InitializeComponent();
MyUserControl myControl = new MyUserControl();
myControl.ButtonClicked += MyControl_ButtonClicked;
this.stMain.Children.Add(myControl);
}
private void MyControl_ButtonClicked(object Sender)
{
//add userControl
MessageBox.Show("Test");
}
MyUserControl.Xaml
<UserControl x:Class="TestProj.MyUserControl"
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:local="clr-namespace:TestProj"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button x:Name="btnAddOtheruserControl" Click="btnAddOtheruserControl_Click" Content="add Another UserControl"> </Button>
</Grid>
</UserControl>
MyUserControl.cs
public partial class MyUserControl : UserControl
{
public event EventAddAnotherUserControl ButtonClicked;
public delegate void EventAddAnotherUserControl(object Sender);
public MyUserControl()
{
InitializeComponent();
}
private void btnAddOtheruserControl_Click(object sender, RoutedEventArgs e)
{
if (ButtonClicked != null)
ButtonClicked(btnAddOtheruserControl);
}
}
I have added a click event on the calendar control. But with my implementation, this event don't work.
My code in Cal.cs control:
#region click
public static RoutedEvent ClickEvent =
EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Cal));
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
protected virtual void OnClick()
{
RoutedEventArgs args = new RoutedEventArgs(ClickEvent, this);
RaiseEvent(args);
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
OnClick();
}
#endregion
XAML code :
<Calen:Cal x:Name="Calendar" Margin="0,50,0,0" Click="Calendar_Click"/>
C# code :
private void Calendar_Click(object sender, RoutedEventArgs e)
{
string t = "";
}
I don't found any solution. I don't know why this code don't work correctly.
Can you help me with this problem please ?
You need to set the DataContext to point to the class that contains the code behind.
<UserControl>
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</UserControl>
This is necessary because unfortunately, by default, the DataContext is not set up correctly in WPF.
For more info on DataContext, see ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext".
On a popup window I have a checkbox.IsChecked bound to a model, but I want to check its state from the xaml code behind when the window is displayed. When checking the checkbox by name in the code behind from the windows loaded event it is not set yet. There are some UI specific things that I need to do and that is why I need to know what the checkboxes value is when the window opens and i cannot perform this from the model that the checkbox.IsChecked is bound to.
The property on the model is set long before the popup window is opened, so it is not an issue of the binding not being there. I figured that once the Loaded event fires the window would be ready to use bindings and all, but this does not seem to be the case.
Xaml:
<RefinedRibbonControls:RefinedRibbonGroup Header="Show Graphs">
<StackPanel x:Name="panelVisibilities">
<CheckBox Content="Show/hide" x:Name="myCheckBox"
IsChecked="{Binding Path=Processor.Model.IsItemVisible}"
Click="GraphVisibilityClickEvent"
HorizontalAlignment="Left"/>
...etc
Property on model:
public bool IsItemVisible
{
get { return _isItemVisible ; }
set
{
if (_isItemVisible != value)
{
_isItemVisible = value;
_propertyChanger.FirePropertyChanged(this, m => m.IsItemVisible);
}
}
}
Event in Xaml codebehind:
private void WindowLoadedEvent(object sender, RoutedEventArgs e)
{
if(myCheckBox.IsChecked.Value)
{
// Do UI Related Stuff
}
}
The binding works fine and the values show up when the window is displayed, the problem is I cannot get the value of the binding in the window loaded event.
Edit: Possible solution I have found but I am not sure if its the best way.
I called the following method from the constructor on the xaml code behind.
private void SetupInitialVisibility()
{
//Fire after everything is loaded.
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
IEnumerable<CheckBox> elements = this.panelVisibilities.Children.OfType<CheckBox>().ToList();
foreach (CheckBox checkBox in elements)
{
if (checkBox.IsChecked != null && checkBox.IsChecked.Value == false)
{
//Do work
}
}
}));
}
found at: https://stackoverflow.com/a/1746975/1253746
Data binding is not done synchronously, but it is delayed. Check this msdn page on dispatcher priorities. It is done at a lower priority than normal window messages, but before rendering.
You could invoke a method on yourself with a lower priority than is defined for databinding, in this method you should be able to safely read the data bound value.
I would still find this ugly. I'd rather subscribe directly to PropertyChanged and check for this property, or even better, rewrite your "UI related code" as a data binding.
P.S. If you start consuming events, be sure to unsubscribe, or you might get memory leaks.
DataBinding should precede the Loaded event I think.
When and how do you set your DataContext? And you are positive that the viewmodel property is already set?
The following works, try to align your code with this if possible.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="myCheckBox" IsChecked="{Binding IsItemVisible}" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (myCheckBox.IsChecked.Value)
{
//...
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemVisible;
public bool IsItemVisible { get { return isItemVisible; } set { isItemVisible = value; OnPropertyChanged("IsItemVisible"); } }
public ViewModel()
{
this.IsItemVisible = true;
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a problem with LostFocus event it does not fire when I click on the background.I read some stuff about focus logic and keyboard focus but I could not find a way to get the focus from a control a like textbox when there is only one of them
XAML:
<Grid Height="500" Width="500">
<TextBox Height="23" Width="120" Margin="12,12,0,0" Name="textBox1" LostFocus="textBox1_LostFocus" />
</Grid>
C#:
private void textBox1_LostFocus(object sender, RoutedEventArgs e)
{
}
You must use the following tunnelling event : PreviewLostKeyboardFocus on your textbox
Tunneling: Initially, event handlers at the element tree root are
invoked. The routed event then travels a route through successive
child elements along the route, towards the node element that is the
routed event source (the element that raised the routed event).
Tunneling routed events are often used or handled as part of the
compositing for a control, such that events from composite parts can
be deliberately suppressed or replaced by events that are specific to
the complete control. Input events provided in WPF often come
implemented as a tunneling/bubbling pair. Tunneling events are also
sometimes referred to as Preview events, because of a naming
convention that is used for the pairs.
The following behavior will fix this:
public class TextBoxUpdateOnLostKeyboardFocusBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
if (AssociatedObject != null)
{
base.OnAttached();
AssociatedObject.LostKeyboardFocus += OnKeyboardLostFocus;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.LostKeyboardFocus -= OnKeyboardLostFocus;
base.OnDetaching();
}
}
private void OnKeyboardLostFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox != null && e.NewFocus == null)
{
// Focus on the closest focusable ancestor
FrameworkElement parent = (FrameworkElement) textBox.Parent;
while (parent is IInputElement && !((IInputElement) parent).Focusable)
{
parent = (FrameworkElement) parent.Parent;
}
DependencyObject scope = FocusManager.GetFocusScope(textBox);
FocusManager.SetFocusedElement(scope, parent);
}
}
}
You can attach it to your TextBox as follows:
<TextBox>
<i:Interaction.Behaviors>
<behaviors1:TextBoxUpdateOnLostKeyboardFocusBehavior />
</i:Interaction.Behaviors>
</TextBox>
I am trying to create a custom event to trigger an animation in Silverlight. Although the event is getting triggered, the animation is not working. The following is the relevant code:
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
MyEvent += new ChangedEventHandler(UserControl_MyEventHandler);
/* Other stuff */
}
private void UserControl_MyEventHandler(object sender, RoutedEventArgs e)
{
MessageBox.Show("MyEventHandler has been called");
}
public delegate void ChangedEventHandler(object sender, RoutedEventArgs e);
private event ChangedEventHandler MyEvent;
private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (MyEvent != null)
MyEvent(this, e);
}
}
}
The XAML code is as follows:
<UserControl
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" mc:Ignorable="d"
x:Class="SilverlightApplication1.MainPage" MouseLeftButtonDown="UserControl_MouseLeftButtonDown">
<Grid x:Name="LayoutRoot" Background="White">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MyEvent">
<ei:GoToStateAction StateName="Highlighted"/>
</i:EventTrigger>
</i:Interaction.Triggers>
...
</Grid>
</UserControl>
Current, the message box containing "MouseLeftButtonDown" is getting displayed but the animation is not getting called. The animation did get called when the EventTrigger EventName was MouseLeftButtonDown instead of MyEvent. Please help me out. Thanks.
You can set SourceName in EventTrigger if you want to trigger on event of some control.
I guess, in your case in code behind:
VisualStateManager.GoToState(this [or some other object with Highlighted state], "Highlighted", false);