WPF, Some Event not fire up from Grid To UserControl - c#

I have a UserControl where i override some event like this:
public class MyControl: UserControl
{
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
// handle mouse event up
}
}
I add this control to antother UserControl -> Grid, this Grid has MouseUp registered.
public class MyParent: UserControl
{
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// handle grid mouse event
}
}
And in MyParent XAML i have simply:
<UserControl ... >
<Grid Name="Grid" MouseUp="Grid_MouseUp">
<my:MyControl Height="250" Width="310" Visibility="Visible" Opacity="1" IsEnabled="True" />
</Grid>
</UserControl>
What i notice is that when i release mouse over MyControl the event is captured by Grid and not routed to MyControl, why?
How can i receive MouseUp event inside MyControl class?
EDIT
With MouseDown all works as expected, only MouseUp not works... and both event are registered for parent Grid too, so what is the difference?
EDIT2
Ok, i think i found the problem, if i add MyControl to MyParent -> Grid directly in XAML, all works good, but if i add it programmatically "MyParentInstance.Grid.Children.Add(MyControlInstance)" then i have the problem above.
Is my code to add control correct?
Thanks.

RoutedEvents only work for the parents of the element, which is invoking the specific event. That means for you, that all parent controls of your grid will receive the event, but children will not. For more information about routed events see Routed Events Overview. To solve your problem I would suggest to register the MouseUp Event at MyControl:
<UserControl ... >
<Grid Name="Grid">
<my:MyControl MouseUp="Grid_MouseUp" Height="250" Width="310" Visibility="Visible" Opacity="1" IsEnabled="True" />
</Grid>
</UserControl>

I did not come across the answer to this question after an extensive search of forums so I'm providing my solution here although this post is dated. I made use of event handlers that I'd already built in the child control by making them public. When the ChildControl user control is added to the Window at the top level, these fire. When nested within parent, the event had to be handled at the parent level.
When programmatically adding a control, add the event handler in the Parent control, such as:
class Parent : UIElement
{
//...
void Parent_AddChildControl()
{
ChildControl child = new ChildControl();
child.MouseEnter += child_MouseEnter;
UiElementX.Children.Add(child);
}
void child_MouseEnter( object sender , MouseEventArgs e )
{
((ChildControl)sender).ChildControl_MouseEnter(sender, e);
}
}
class ChildControl : UIElement
{
//...
public void ChildControl_MouseEnter(object sender, MouseEventArgs e)
{
//event handling
}
}

Related

Avalonia: Binding Command Property to UserControl

So I created a custom button control, let's call it MyButton, using Avalonia. MyButton is a collection of several controls including a Avalonia.Controls.Button looking like this (MyButton.xaml):
<Border xmlns="https://github.com/avaloniaui"
.....
x:Class="myProject.myControls.MyButton">
<Button x:Name="button"
Background="Transparent"
....
BorderThickness="0">
<Panel >
.....
</Panel>
</Button>
</Border>
(Yes my custom control inherits from Avalonia.Controls.Border instead of Avalonia.Controls.UserControl)
My plan is to pass the buttons Command property (the one with the x:Name="button" attribute) further up and make it accessable via MyButton.
So when I want to use MyButton in the MainWindow.xaml I'd to be able to do the following:
<Window ... >
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<myControls:MyButton Command="{Binding MyButton_Click}"/>
</Window>
where the view model MainWindowViewModel.cs looks like this:
public partial class MainWindowViewModel : ViewModelBase
{
public void MyButton_Click()
{
// do stuff...
}
}
The way I tried to do this in MyButton.xaml.cs is the following:
public class MyButton : Border
{
private readonly Button button;
public MyButton()
{
InitializeComponent();
button = this.FindControl<Button>("button");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public static readonly StyledProperty<ICommand> CommandProperty =
AvaloniaProperty.Register<MyButton, ICommand>(nameof(Command));
public ICommand Command
{
get { return GetValue(CommandProperty); }
set
{ // this setter is never executed as can be seen when running with debugger attached
if (button != null)
{
button.Command = value;
SetValue(CommandProperty, value);
}
else
{
Debug.WriteLine("MyButton error: unable to set Command: control not initialized!");
}
}
}
}
However when running the application and clicking the button the target method MyButton_Click is never executed. Attaching the debugger it seems like the MyButton.Command setter is never executed either, which I think would be due to incorrect Binding? (There are no binding errors or anything related to this on the debug console)
After a few hours of trial and error I found a workaround using Reflection and a custom OnClick() Eventhandler on the button element. It works but is kinda ugly and requires a static target method so my question is:
How does one properly bind a Command on a UserControl to a method contained in the ViewModel of the main Window?
Also: Could my reflection-based approach also be viable? (I assume Avalonia bindings are also based on reflection somehow?)
Do not use getters and setters for styled properties, they won't be called when property is altered via bindings, styles or animations (it's the same for WPF, UWP and Xamarin.Forms). Instead you need to either bind your nested Button's command via <Button Command="{Binding $parent[myControls:MyButton]}" /> (preferable) or subscribe to property change notification from the static constructor like the original Button does.
More on dependency properties (which work mostly the same way as StyledProperty in Avalonia): https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview

How to run a method every time a TabItem is selected, in an MVVM application using Prism

I have been trying to implement this for a while and haven't been able to do it so far, despite having the feeling that this should be something easy.
The difficulty comes from the fact that I have implemented a WPF application using the MVVM pattern. Now, this is my first attempt at both the pattern and the framework, so it is almost guaranteed that I have made mistakes while trying to follow the MVVM guidelines.
My implementation
I have three Views with their respective ViewModels (wired using Prism's AutoWireViewModel method). The MainView has a TabControl with two TabItems, each of witch contains a Frame container with the Source set to one of the other two Views. The following code is an excerpt of the MainView:
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Test">
<!--TestView-->
<Frame Source="View1.xaml"/>
</TabItem>
<TabItem Header="Results">
<!--ResultsView-->
<Frame Source="View2.xaml"/>
</TabItem>
</TabControl>
My problem
Every time that someone changes to a specific TabItem, I would like to run a method that updates one of the WPF controls included in that View. The method is already implemented and bound to a Button, but ideally, no button should be necessary, I would like to have some kind of Event to make this happen.
I appreciate all the help in advance.
You could for example handle the Loaded event of the Page to either call a method or invoke a command of the view model once the view has been loaded initially:
public partial class View2 : Page
{
public View2()
{
InitializeComponent();
Loaded += View2_Loaded;
}
private void View2_Loaded(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel2;
if (viewModel != null)
viewModel.YourCommand.Execute(null);
Loaded -= View2_Loaded;
}
}
The other option would be handle this in the MainViewModel. You bind the SelectedItem property of the TabControl to a property of the MainViewModel and set this property to an instance of either ViewModel2 or ViewModel2, depending on what kind of view you want to display.
You could then call any method or invoked any command you want on these. But this is another story and then you shouldn't hardcode the TabItems in the view and use Frame elements to display Pages. Please take a look here for an example:
Selecting TabItem in TabControl from ViewModel
Okay, so What I have done is Create a Custom Tab Control. I will write out step by step instructions for this, and then you can add edit to it.
Right click on your solution select add new project
Search For Custom Control Library
High Light the name of the class that comes up, and right click rename it to what ever you want I named it MyTabControl.
Add Prism.Wpf to the new project
Add a reference to the new project to where ever your going to need it. I needed to add to just the main application, but if you have a separate project that only has views then you will need to add it to that too.
Inherit your Custom Control From TabControl Like:
public class MyTabControl : TabControl
You will notice that there is a Themes folder in the project you will need to open the Generic.xaml and edit it. it should look like:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" for some reason this will not let me show the style tags but they will need to be in there as well
Please review this code I got this from Add A Command To Custom Control
public class MyTabControl : TabControl
{
static MyTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
}
public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
"TabChangedCommand", typeof(ICommand), typeof(MyTabControl),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandCallBack)));
private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myTabControl = (MyTabControl)d;
myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
}
private void HookupCommands(ICommand oldValue, ICommand newValue)
{
if (oldValue != null)
{
RemoveCommand(oldValue, oldValue);
}
AddCommand(oldValue, oldValue);
}
private void AddCommand(ICommand oldValue, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
var canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.TabChangedCommand != null)
{
if (TabChangedCommand.CanExecute(null))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
public ICommand TabChangedCommand
{
get { return (ICommand) GetValue(TabChangedCommandProperty); }
set { SetValue(TabChangedCommandProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TabChangedCommand != null)
{
TabChangedCommand.Execute(null);
}
}
}
you will need to add the name space in your window or usercontrol like:
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
and here is your control:
<wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
<TabItem Header="View A"></TabItem>
<TabItem Header="View B"></TabItem>
</wpfCustomControlLibrary1:MyTabControl>
This is how I'd approach this sort of requirement:
View:
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="tc" ItemsSource="{Binding vms}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:uc1vm}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:uc2vm}">
<local:UserControl2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeading}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
When it has a uc1vm it will be templated into usercontrol1 in the view.
I'm binding to a collection of viewmodels which all implement an interface so I know for sure I can cast to that and call a method.
Main viewmodel for window:
private IDoSomething selectedVM;
public IDoSomething SelectedVM
{
get { return selectedVM; }
set
{
selectedVM = value;
selectedVM.doit();
RaisePropertyChanged();
}
}
public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
{ new uc1vm(),
new uc2vm()
};
public MainWIndowViewModel()
{
}
When a tab is selected, the setter for selected item will be passed the new value. Cast that and call the method.
My interface is very simple, since this is just illustrative:
public interface IDoSomething
{
void doit();
}
An example viewmodel, which is again just illustrative and doesn't do much:
public class uc1vm : IDoSomething
{
public string TabHeading { get; set; } = "Uc1";
public void doit()
{
// Your code goes here
}
}
I appreciate all of your input, but I found an alternative solution. Given the information given by #mm8, I took advantage of the Loaded event but in a way that does not require any code in the code behind.
My solution
In the View which I would like to give this ability to execute a method every time the user selects the TabItem that contains it, I added the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then simply implemented a DelegateCommand called OnLoadedCommand in the View's respective ViewModel. Inside that command I call my desired method.
Please comment if you spot anything wrong with this approach! I chose to try this since it required the least amount of changes to my code, but I may be missing some vital information regarding problems the solution may cause.

Detect global click UWP

I have a visible grid that has to collapse when I click outside of it. I solved it for half.
MainPage.xaml code:
<StackPanel Width="400" VerticalAlignment="Center">
<Button x:Name="btnOne" Content="Button One" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Button x:Name="btnShowGrid" Content="Show Grid" Click="btnShowGrid_Click" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="30"/>
<Grid x:Name="ControlGrid" PointerEntered="ControlGrid_PointerEntered" PointerExited="ControlGrid_PointerExited" HorizontalAlignment="Center" Visibility="Visible" Background="LightGreen" Height="300" VerticalAlignment="Top" Width="300" Margin="30"/>
</StackPanel>
MainPage.xaml.cs code:
bool PointerInGrid = false;
public MainPage()
{
this.InitializeComponent();
}
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
base.OnPointerPressed(e);
if (!PointerInGrid)
{
ControlGrid.Visibility = Visibility.Collapsed;
}
}
private void btnShowGrid_Click(object sender, RoutedEventArgs e)
{
ControlGrid.Visibility = Visibility.Visible;
}
private void ControlGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = true;
}
private void ControlGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
PointerInGrid = false;
}
If I click into the Grid this remains visible, if I click out the grid goes to collapsed, so far everything is fine but, if I click the btnOne the grid remains visible.
So is possible detect a global click to collapse the grid?
As always thanks in advance.
Code that works in windows form:
const int WM_PARENTNOTIFY = 0x0210;
const int WM_LBUTTONDOWN = 0x0201;
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (!DesignMode)
{
if (m.Msg == WM_PARENTNOTIFY)
{
if (m.WParam.ToInt32() == WM_LBUTTONDOWN)
{
MessageBox.Show("Clicked!");
}
}
}
base.WndProc(ref m);
}
The PointerPressed event is swallowed by the button so it cannot reach the underlying parent, where the event handler OnPointerPressed is executed.
There is a technique to pass the event down to the parent, please refer to this answer.
You can do it like this, in the page's constructor
public MainPage()
{
this.InitializeComponent();
//...other code
//then add this line
this.btnOne.AddHandler(UIElement.PointerPressedEvent,
new PointerEventHandler((s, e) => { e.Handled = false; }), true);
}
A second thought: Maybe you can just handle btnOne’s PointerPressed event, and in the handler set its event argument’s Handled property as false. Don’t need to use AddHandler, I mean. Give it a try.
As far as I known, the UWP is sandboxed, we can't use WndProc method to receive Window event Messages.
Specific Windows Runtime controls may have class-based handling for the PointerPressed input event. If so, the control probably has an override for the method OnPointerPressed. Typically the event is marked handled by the class handler, and the PointerPressed event is not raised for handling by any user code handlers on that control. For example, ButtonBase has class handling that handles PointerPressed and instead fires Click.
For more info, please refer PointerPressed.
When we click the Button, the OnPointerPressed event will not be fired.
If you want to your Button can fire OnPointerPressed event, you can create a class that inherited the Button class. In the class, you can override the OnPointerPresse event.
For example:
class MyButton: Button
{
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
}
}
By the way, if we override OnPointerPressed event in class that inherited the Button class, the Click event will not be fired.

How do I access a page frame to navigate a page through a UserControl object in a UWP?

I'm developing a UWP application that involves several UserControl objects inside a Map using Windows.UI.Xaml.Navigation.
Sometimes, the user should be able to click a button in these objects to go to a new page. However, I can't access the page's frame, so I can't use the following method.
Frame.Navigate(typeof([page]));
How could I access the page frame to use the method?
Let me know of any alternatives; I've been stuck on this for most of the day! Thanks in advance for any help you guys offer!
We can let the page to navigate itself. Just define an event in your custom usercontrol and listen to the event in its parent(the page).
Take the following as an example:
Create a custom user control and put a button on it for test purpose.
In test button's click event, raise the event to navigate parent page.
In Parent page, listen to the UserControl's event and call Frame.Navigate.
MyControl's Xaml:
<UserControl
x:Class="App6.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Button x:Name="testbtn" Margin="168,134,0,134" Click="testbtn_Click">test</Button>
</Grid>
</UserControl>
MyControl's CodeBehind:
public sealed partial class MyControl : UserControl
{
public delegate void MyEventHandler(object source, EventArgs e);
public event MyEventHandler OnNavigateParentReady;
public MyControl()
{
this.InitializeComponent();
}
private void testbtn_Click(object sender, RoutedEventArgs e)
{
OnNavigateParentReady(this, null);
}
}
Navigate MainPage to SecondPage:
public MainPage()
{
this.InitializeComponent();
myControl.OnNavigateParentReady += myControl_OnNavigateParentReady;
}
private void MyControl_OnNavigateParentReady(object source, EventArgs e)
{
Frame.Navigate(typeof(SecondPage));
}
You could get a reference to the Frame from the Current Window's Content.
In your user control's code behind try:
Frame navigationFrame = Window.Current.Content as Frame;
navigationFrame.Navigate(typeof([page]));
Or, with Cast=>
((Frame)Window.Current.Content).Navigate(typeof(Views.SecondPage));

Bubbling Events not Occurring

In the code below, I am seeing the tunneling events occurring but am not seeing the corresponding bubbling events occurring. Why might this be?
Thanks,
Dave
<Window x:Class="TestRoutedEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
PreviewMouseUp="Window_PreviewMouseUp"
MouseUp="Window_MouseUp">
<Grid Background="Brown" Margin="30"
PreviewMouseUp="Grid_PreviewMouseUp"
MouseUp="Grid_MouseUp">
<TextBlock Text="Press me" HorizontalAlignment="Center" VerticalAlignment="Center"
Background="LightGray"
Padding="3"
PreviewMouseUp="TextBlock_PreviewMouseUp"
MouseUp="TextBlock_MouseUp"/>
</Grid>
</Window>
namespace TestRoutedEvents
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void TextBlock_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("TextBlock_PreviewMouseUp");
}
private void Grid_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Grid_PreviewMouseUp");
}
private void Window_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Window_PreviewMouseUp");
}
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("TextBlock_MouseUp");
}
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Grid_MouseUp");
}
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("Window_MouseUp");
}
}
}
Actually event is raising but you are not able to get that. The reason is that the messagbox takes the focus from the window when it pops up. Thus, the UI elements in the routed event chain won't receive the routed event any more.
you can have a control to your window and add event details into that to confirm about it.
e.g. Add ListBox in xaml and name it like listBox and then use this code in each of your handler
listBox.Items.Add(sender+"\n"+e.RoutedEvent.Name+"\n"+e.RoutingStrategy);
This is a quote from MSDN:
TextBox has built-in handling for the
bubbling MouseUp and events.
Consequently, custom event handlers
that listen for MouseUp or MouseDown
events from a TextBox will never be
called. If you need to respond to
these events, listen for the tunneling
PreviewMouseUp and PreviewMouseDown
events instead.
The article is about TextBox not TextBlock, but I tested this on other UIElement-derived controls, and they all seemed to behave the same way. I'm assuming this is at the base class level somewhere in the hierarchy.
How to: Handle MouseUp and MouseDown Events for a TextBox
I don't know what you are trying to do with this, but perhaps this post will help. It confirms what Anurag is saying, regarding the messagebox taking the focus from your window.
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f4d609d4-ba2c-478e-aa53-9ee557ea5165

Categories