WPF: Canvas swallowing MouseDownEvent? - c#

Can anybody please explain to me why theMouseDownevent is not reaching theScrollViewerin this easy example?
<Window x:Class="MouseDownTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ScrollViewer Name="scrollViewer" Background="Green" MouseDown="ScrollViewer_MouseDown" PreviewMouseDown="ScrollViewer_PreviewMouseDown">
<Canvas Name="canvas" Background="Beige" MouseDown="Canvas_MouseDown" PreviewMouseDown="Canvas_PreviewMouseDown">
</Canvas>
</ScrollViewer>
</Grid>
</Window>
code behind:
using System;
using System.Windows;
using System.Windows.Input;
namespace MouseDownTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
scrollViewer.AddHandler(MouseDownEvent, new RoutedEventHandler(ScrollViewer_Test));
}
private void ScrollViewer_Test(object sender, RoutedEventArgs routedEventArgs)
{
Console.WriteLine("ScrollViewer_Test");
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Canvas_MouseDown");
}
private void Canvas_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("Canvas_PreviewMouseDown");
}
private void ScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("ScrollViewer_MouseDown");
}
private void ScrollViewer_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Console.WriteLine("ScrollViewer_PreviewMouseDown");
}
}
}
Output when clicking canvas is
ScrollViewer_PreviewMouseDown
Canvas_PreviewMouseDown
Canvas_MouseDown
Why isScrollViewer_MouseDownbeing omitted? I already looked into this article but the solution given there does not help (or I did it wrong).

You will probably need to set Focusable="False" on the ScrollViewer to allow the mouse events to pass though
<ScrollViewer Name="scrollViewer" Focusable="False" Background="Green" />
Output:
ScrollViewer_PreviewMouseDown
Canvas_PreviewMouseDown
Canvas_MouseDown
ScrollViewer_MouseDown

Related

PointerPoint.Properties.MouseWheelDelta tilt or scroll

I have a function that listenes to PointerWheelChanged events of my UIElement.
I can retreive the MouseWheelDelta but this doesn't tell me if the mouse wheel was tilted or moved up/down.
How can I get this info?
This is my code to get the delta:
private void TestScrollViewer_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
PointerPoint ptrPt = e.GetCurrentPoint((UIElement)sender);
int delta = ptrPt.Properties.MouseWheelDelta;
// positive: forward/right motion; negative: backward/left motion
}
You can use IsHorizontalMouseWheel inside Pointers. See this example below:
MainWindow.xaml
<Window
x:Class="MouseWheelTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:MouseWheelTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0">
<TextBlock x:Name="WheelHorizontal" />
<TextBlock x:Name="WheelVertical" />
</StackPanel>
<Grid
Grid.Row="1"
Background="Transparent"
PointerWheelChanged="Grid_PointerWheelChanged" />
</Grid>
</Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
namespace MouseWheelTests;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private int WheelHorizontalValue { get; set; }
private int WheelVerticalValue { get; set; }
private void Grid_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var properties = e.GetCurrentPoint((UIElement)sender).Properties;
if (properties.IsHorizontalMouseWheel is true)
{
WheelHorizontalValue += properties.MouseWheelDelta;
this.WheelHorizontal.Text = $"Horizontal: {WheelHorizontalValue}";
}
else
{
WheelVerticalValue += properties.MouseWheelDelta;
this.WheelVertical.Text = $"Vertical: {WheelVerticalValue}";
}
}
}

AssociatedObject.DataContext is null in Behavior in ContentPresenter

I try to use a Behavior to trigger a method in the View from the VM. To this end, I use an object of type Trigger with an Event and invoker in the VM, and bind this to a dependency property in the behavior. The behavior does the subscription to the event in its Loaded callback. This works as expected, when the event in the VM is invoked, I can call the method in the view using AssociatedObject.
However, when the Behavior is inside a DataTemplate for a ContentPresenter, I see the following weird behavior (no pun intended...). Given a ContentPresenter with two DataTemplates, used depending on the type of Content (Tab1 or Tab2), when the Content is changed from Tab1 to Tab2, everything is fine. When it is changed from one instance of Tab1 to another instance of Tab1, however, AssociatedObject.DataContext is suddently null (and the method I try to call on the View fails due to this).
I tried to create a minimal example to demonstrate this. The code below should have everything to just run in a new WPF app. Observe the debug output in the following click paths to reproduce:
Tab -> Tab1 -> Invoke -> Output as expected
Tab -> Tab1a -> Tab1 -> Invoke -> DataContext is empty
Tab -> Tab1 -> Again as expected
I can think of ways to work around this problem, but I would like to understand it. I assume it is related to the DataTemplate not being rebuilt when the Content changes to the same type, but still I would expect AssociatedObject to point to the correct Grid (which I think it does not, since the DataContext in the actually shown Grid is fine). Any ideas are highly appreciated!
MainWindow.xaml
<Window x:Class="EmptyWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:EmptyWpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Content="{Binding Tab}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:Tab1}">
<Grid>
<i:Interaction.Behaviors>
<local:TestBehavior Trigger="{Binding T}"/>
</i:Interaction.Behaviors>
<TextBlock>With behavior</TextBlock>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Tab2}">
<Grid>
<TextBlock>Empty</TextBlock>
</Grid>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<Button Grid.Row="1" Click="ButtonBase_OnClick">Tab</Button>
<Button Grid.Row="1" Grid.Column="1" Click="ButtonBase_OnClick1">Tab1</Button>
<Button Grid.Row="1" Grid.Column="2" Click="ButtonBase_OnClick1a">Tab1a</Button>
<Button Grid.Row="2" Grid.ColumnSpan="3" Click="ButtonBase_OnClick2">Invoke on VM</Button>
</Grid>
MainWindows.xaml.cs:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using EmptyWpfApp.Annotations;
namespace EmptyWpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Tab2 _tab = new Tab2();
private Tab1 _tab1 = new Tab1();
private Tab1 _tab1a = new Tab1();
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((ViewModel)DataContext).Tab = _tab;
}
private void ButtonBase_OnClick1(object sender, RoutedEventArgs e)
{
((ViewModel)DataContext).Tab = _tab1;
}
private void ButtonBase_OnClick1a(object sender, RoutedEventArgs e)
{
((ViewModel)DataContext).Tab = _tab1a;
}
private void ButtonBase_OnClick2(object sender, RoutedEventArgs e)
{
(((ViewModel) DataContext).Tab as Tab1)?.T.OnE();
}
}
public class ViewModel : INotifyPropertyChanged
{
private ITab _tab;
public ITab Tab
{
get => _tab;
set
{
_tab = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public interface ITab {}
public class Tab1 : ITab
{
public Trigger T { get; } = new Trigger();
}
public class Tab2 : ITab {}
public class Trigger
{
public event EventHandler E;
public virtual void OnE()
{
E?.Invoke(this, EventArgs.Empty);
}
}
public class TestBehavior : Behavior<Grid>
{
public static readonly DependencyProperty TriggerProperty = DependencyProperty.Register(
"Trigger",
typeof(Trigger),
typeof(TestBehavior),
new PropertyMetadata(default(Trigger)));
public Trigger Trigger {
get => (Trigger)GetValue(TriggerProperty);
set => SetValue(TriggerProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
AssociatedObject.Unloaded += Cleanup;
}
private void Cleanup(object sender, RoutedEventArgs e)
{
Cleanup();
}
protected override void OnDetaching()
{
base.OnDetaching();
Cleanup();
}
private void Cleanup()
{
AssociatedObject.Loaded -= OnLoaded;
if (Trigger != null)
Trigger.E -= TriggerOnE;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Subscribe();
}
private void Subscribe()
{
Trigger.E += TriggerOnE;
}
private void TriggerOnE(object sender, EventArgs e)
{
Debug.WriteLine("DC:" + AssociatedObject.DataContext);
}
}
}

checkbox checked or click event not firing in datatemplate

I have a checkbox in my datatemplate and for some reason the events are not firing. see code below. my datatemplate is in a resource dictionary with code behind file. Any Ideas?
<ResourceDictionary x:Class="ArmyBuilder.Templates.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="{Binding Name}" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
//code behind
namespace ArmyBuilder.Templates
{
public partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
I am not sure how you use it, but the your code works for me and the click event got fired. Check the following and if you still cannot find the point, share a repro project to show how you used it.
Template XAML:
<ResourceDictionary x:Class="App10.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="Click Me" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
Template cs:
namespace App10
{
public sealed partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
this.InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
In MainPage.Xaml, use the template in a ListView:
<Page
x:Class="App10.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App10"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:EquipmentDataTemplate></local:EquipmentDataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView" CanReorderItems="True" AllowDrop="True" ItemTemplate="{StaticResource EquipmentDataTemplate}">
</ListView>
</Grid>
</Page>
In MainPage cs:
namespace App10
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var list = new ObservableCollection<string>();
for (int i = 0; i < 10; i++)
{
list.Add("Item " + i);
}
listView.ItemsSource = list;
}
}
}

How to get 2 UserControls synchronized or access with Buttons / Code Behind instances in WPF?

I am having a problem in my project. This is my Solution Explorer:
BLA is an example UserControl that is loaded twice into a MainWindow Grid:
<Window x:Class="UserControlWechseln.MainWindow"
xmlns:local="clr-namespace:UserControlWechseln"
Title="MainWindow" Height="428" Width="1195" xmlns:am="http://schemas.amcharts.com/charts/wpf/2009/xaml">
<Grid Height="1000" Width="1000">
<Grid Height="500" HorizontalAlignment="Left" Margin="12,31,0,0" Name="grid1" VerticalAlignment="Top" Width="142">
<local:BLA Margin="-3,-17,-270,17" />
</Grid>
<Grid Height="500" HorizontalAlignment="Right" Margin="0,6,31,0" Name="grid2" VerticalAlignment="Top" Width="402">
<local:BLA />
</Grid>
</Grid>
</Window>
The XAML Code for the BLA UserControl looks like this:
<UserControl x:Class="UserControlWechseln.BLA"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Name="Bla">
<Button Content="House" Height="23" HorizontalAlignment="Left" Margin="64,237,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="ButtonClick" />
<Button Content="Soccer" Height="23" HorizontalAlignment="Left" Margin="169,237,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="ButtonClick" />
</Grid>
</UserControl>
And the C# Code for the BLA UserControl looks like this:
public partial class BLA : UserControl {
public BLA() {
InitializeComponent();
}
private void ButtonClick(object sender, RoutedEventArgs e) {
Button btn = sender as Button;
Bla.Children.Clear();
if (btn.Content.ToString() == "House") {
Haus uc1 = new Haus();
Bla.Children.Add(uc1);
} else if (btn.Content.ToString() == "Soccer") {
Fussball uc2 = new Fussball();
Bla.Children.Add(uc2);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
}
private void button1_Click(object sender, RoutedEventArgs e) {
}
private void button1_Click_1(object sender, RoutedEventArgs e) {
}
private void button1_Click_2(object sender, RoutedEventArgs e) {
}
private void button2_Click(object sender, RoutedEventArgs e) {
}
}
}
The problem now is that you get this view:
Is it possible to trigger the Home Button once and the Img in displays on both UserControls "BLA"? They obviously are the same UserControl.
Thanks for your help.
They are different instances of your UserControl, they do not know that the other exists. You are going to need to create Properties and Events. I am sure there is probably a better way of it but here is something I threw together to demonstrate( I also renamed some of your classes because I do not have the same Namespaces that you have). Also you are clearing all of the controls out of your UserControl when you change your image.
Edit added complete example
UserControl
namespace WpfApplication1{
/// <summary>
/// Interaction logic for Bla.xaml
/// </summary>
public partial class Bla : UserControl
{
public event RoutedEventHandler changeHaus
{
add { AddHandler(myEvents.changeHausEvent, value); }
remove { RemoveHandler(myEvents.changeHausEvent, value); }
}
public event RoutedEventHandler changeSoccer
{
add { AddHandler(myEvents.changeSoccerEvent, value); }
remove { RemoveHandler(myEvents.changeSoccerEvent, value); }
}
public Bla()
{
InitializeComponent();
}
private void ButtonClick(object sender, RoutedEventArgs e) {
Button btn = sender as Button;
if (btn.Content.ToString() == "House")
{
SetHaus();
RoutedEventArgs ea = new RoutedEventArgs(myEvents.changeHausEvent);
RaiseEvent(ea);
}
else if (btn.Content.ToString() == "Soccer")
{
SetSoccer();
RoutedEventArgs ea = new RoutedEventArgs(myEvents.changeSoccerEvent);
RaiseEvent(ea);
}
}
public void SetHaus()
{
display.Fill = new SolidColorBrush(Colors.Blue);
}
public void SetSoccer()
{
display.Fill = new SolidColorBrush(Colors.Red);
}
}
public static class myEvents
{
public static RoutedEvent changeHausEvent = EventManager.RegisterRoutedEvent("changeHaus",RoutingStrategy.Tunnel,typeof(RoutedEventHandler), typeof(Bla));
public static RoutedEvent changeSoccerEvent = EventManager.RegisterRoutedEvent("changeSoccer", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(Bla));
}
}
UserControl Xaml
<UserControl x:Class="WpfApplication1.Bla"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Name="bla">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Name="display" Height="223" Grid.Row="0" />
<Button Grid.Row="1" Content="House" Height="23" HorizontalAlignment="Left" Margin="64,25,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="ButtonClick" />
<Button Grid.Row="1" Content="Soccer" Height="23" HorizontalAlignment="Left" Margin="169,25,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="ButtonClick" />
</Grid>
</UserControl>
Main Window
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void bla1_changeHaus(object sender, RoutedEventArgs e)
{
bla2.SetHaus();
}
private void bla1_changeSoccer(object sender, RoutedEventArgs e)
{
bla2.SetSoccer();
}
private void bla2_changeHaus(object sender, RoutedEventArgs e)
{
bla1.SetHaus();
}
private void bla2_changeSoccer(object sender, RoutedEventArgs e)
{
bla1.SetSoccer();
}
}
}
MainWindow Xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication1">
<Grid>
<my:Bla HorizontalAlignment="Left" Margin="0,12,0,0" x:Name="bla1" VerticalAlignment="Top" changeHaus="bla1_changeHaus" changeSoccer="bla1_changeSoccer" />
<my:Bla HorizontalAlignment="Left" Margin="247,12,0,0" x:Name="bla2" VerticalAlignment="Top" changeHaus="bla2_changeHaus" changeSoccer="bla2_changeSoccer" />
</Grid>
</Window>

Using C#, can you drag a canvas in WPF?

Can you drag a canvas in WPF? How do you set the position of the canvas? Here is what I got so far:
/// xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525"
WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True"
Background="Transparent" Loaded="MainWindow_Loaded">
<Canvas Name="ParentCanvas" Background="#FF6E798D">
</Canvas>
</Window>
/// code behind
public partial class MainWindow : Window
{
private Boolean isMouseCapture;
public MainWindow()
{
InitializeComponent();
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.ParentCanvas.MouseLeftButtonDown += new MouseButtonEventHandler(_MouseLeftButtonDown);
this.ParentCanvas.MouseLeftButtonUp += new MouseButtonEventHandler(_MouseLeftButtonUp);
this.ParentCanvas.MouseMove += new MouseEventHandler(_MouseMove);
}
void _MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.ParentCanvas.ReleaseMouseCapture();
isMouseCapture = false;
}
void _MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.ParentCanvas.CaptureMouse();
isMouseCapture = true;
}
void _MouseMove(object sender, MouseEventArgs e)
{
if (isMouseCapture)
{
this.ParentCanvas.X= e.GetPosition(this).X;
this.ParentCanvas.Y = e.GetPosition(this).Y;
}
}
}
'X' is not a property of Canvas (i.e."this.ParentCanvas.X"). What do I use to set the position?
To set the position of an element in pixels, the element must be contained in a Canvas panel.
You can then call Canvas.SetTop and Canvas.SetLeft.

Categories

Resources