I have a problem with a PreviewMouseDown event. I have an app that does some complex drawing in a DrawingVisual which is later added on the canvas.
I've made a minimal working demonstration of the problem.
Steps to reproduce:
Run an app
Click Render
Click inside a red area.
PreviewMouseDown event on the Canvas is firing on the green area but doesn't fire at the red one. I really need to capture the mouse events inside this red area. How can this be accomplished?
Here the XAML:
<Window x:Class="MouseDownTest.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Click="Button_Click">Render</Button>
<Canvas Grid.Row="1" x:Name="HostCanvas" PreviewMouseDown="HostCanvas_PreviewMouseDown"
Background="Green"
Width="300" Height="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Window>
And here is the code behind:
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace MouseDownTest
{
public partial class MainWindow : Window
{
public class VisualHost : UIElement
{
public Visual Visual { get; set; }
public VisualHost(Visual childVisual) { Visual = childVisual; }
protected override int VisualChildrenCount => (Visual is null) ? 0 : 1;
protected override Visual GetVisualChild(int index) { return Visual; }
} // class VisualHost
public MainWindow() => InitializeComponent();
// Not FIRING inside a red square
private void HostCanvas_PreviewMouseDown(object sender, MouseButtonEventArgs e) => MessageBox.Show("Got a click!");
private void Render()
{
this.HostCanvas.Children.Clear();
this.HostCanvas.InvalidateVisual();
DrawingVisual vis = new();
using (DrawingContext dc = vis.RenderOpen())
{
var rect = new Rect(50, 50, 50, 50);
var redbrush = new SolidColorBrush(Colors.Red);
var redpen = new Pen(redbrush, 2);
dc.DrawRectangle(redbrush, redpen, rect);
}
var vh = new VisualHost(vis);
this.HostCanvas.Children.Add(vh);
System.Diagnostics.Debug.Assert(this.HostCanvas.Children.Count > 0);
}
private void Button_Click(object sender, RoutedEventArgs e) => Render();
}
}
So, how can I capture the mouse inside a red area and why exactly PreviewMouseDown is not firing?
Related
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}";
}
}
}
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);
}
}
}
I have a vertical ListView using virtualization, and I subscribe to the ContainerContentChanging event. If I insert items into the top of the list, and attempt to get their transform in the handler for ContainerContentChanging using TransformToVisual(Window.Current.Content), as soon as the list has enough items to start virtualizing, I begin getting values like -15000 for the transforms matrix OffsetX and OffsetY values. Since I'm inserting the item at the top, it's clearly rendering (it has appropriate calculated width/height, etc.), so I'm not sure why the transform would be bad.
Here's some code if it helps. It doesn't get much more basic.
XAML:
<Page
x:Class="TestListView.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestListView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="400"/>
</Grid.RowDefinitions>
<Button
Content="Add Item"
Click="OnAddItemClick"/>
<ListView
Grid.Row="1"
ItemsSource="{x:Bind Items, Mode=OneWay}"
ContainerContentChanging="OnContainerContentChanging"/>
</Grid>
</Page>
Code-Behind:
public sealed partial class MainPage : Page
{
public readonly ObservableCollection<string> Items = new ObservableCollection<string>();
public MainPage()
{
this.InitializeComponent();
Items.Add("Test 2");
Items.Add("Test 1");
Items.Add("Test 0");
}
private void OnAddItemClick(object sender, RoutedEventArgs e)
{
Items.Insert(0, $"Test {Items.Count}");
}
private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
var addedItem = args?.ItemContainer;
if (addedItem == null)
return;
var transform = addedItem.TransformToVisual(Window.Current.Content);
}
}
I know my question sounds basic, but i searched all over the place and found nothing..
this is my code :
public MainWindow()
{
InitializeComponent();
Map newMap = new Map();
newMap.setMapStrategy(new SmallMapStrategy());
newMap.createMap();
System.Windows.Forms.PictureBox pictureBox1 = new System.Windows.Forms.PictureBox();
pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(newMap.grid[3].afficher);
}
this is the afficher function :
public override void afficher(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(squareImage, pos_x, pos_y, 50, 50);
}
squareImage is an attribute corresponding to a Drawing.Image.
pos_x and pos_y are custom int32 attributes.
What i'd like is to SEE the image while running my application...
Since the PictureBox that you are using is a Winforms Control you will need to add a WindowsFormsHost Control to your Wpf Form and add the PictureBox to that. Any time you dynamically create a control you need to add it to the Form or Container object otherwise it will not be shown.
But first, add these references:
System.Windows.Forms
WindowsFormsIntegration
Now write code something like this.
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">
<Grid>
<WindowsFormsHost Height="175" HorizontalAlignment="Left" Margin="10,10,0,0" Name="windowsFormsHost1" VerticalAlignment="Top" Width="255" />
</Grid>
</Window>
MainWindow.xaml.cs
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
System.Windows.Forms.PictureBox picturebox1 = new System.Windows.Forms.PictureBox();
windowsFormsHost1.Child = picturebox1;
picturebox1.Paint += new System.Windows.Forms.PaintEventHandler(picturebox1_Paint);
}
void picturebox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(#"C:\Temp\test.jpg");
System.Drawing.Point ulPoint = new System.Drawing.Point(0, 0);
e.Graphics.DrawImage(bmp,ulPoint);
}
}
}
I have a simple app where after clicking a button the value of a label is updated every second.I'm doing this as a POC for a progress bar control that I want to develop.
I would like to know if there is a way to apply some kind of scroller animation to the label which will:
1) When the content of a label is updated it will scroll the new value from the top and the old one will be scrolled down and disappear from view(Hope this makes sence).
I know that this could probably be achieved with some kind of animation but I couldn't find any helpful examples on the web if anyone knows how this can be done please share your expertise:
View:
<Window x:Class="WpfApplication1.ScrollerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scroller" DataContext="{StaticResource scrollerVM}" Height="150" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Messages}" Width="200" Height="50" BorderThickness="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Width="70" Height="24" Content="Add new" Command="{Binding AddNew}" HorizontalAlignment="Left" Margin="0,56,0,30" />
</Grid>
</Window>
View model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
namespace WpfApplication1.Scroller
{
public class Message
{
public Message(string _text)
{
text = _text;
}
private string text;
public string Text
{
get { return text; }
set {text = value;}
}
}
public class ScrollerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand AddNew { get; protected set; }
ObservableCollection<Message> _messages = new ObservableCollection<Message>();
public ObservableCollection<Message> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged("Messages");
}
}
public ScrollerViewModel()
{
AddNew = new DelegateCommand(Add);
}
private void Add(object parameter)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new System.EventHandler(timer_Tick);
timer.Interval = new System.TimeSpan(0, 0, 1);
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
Messages.Clear();
Messages.Add(new Message(DateTime.Now.ToString("ss")));
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
More comprehensive/different examples here.
The following will result in a basic vertical marquee (scrolling text block).
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" Loaded="Window_Loaded">
<Canvas Name="canvas1" >
<TextBlock Name="textBlock1">Hello</TextBlock>
</Canvas>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BeginAnimation()
{
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = -textBlock1.ActualHeight;
doubleAnimation.To = canvas1.ActualHeight;
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(3));
textBlock1.BeginAnimation(Canvas.TopProperty, doubleAnimation);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BeginAnimation();
}
}
First, you'll want "smooth scrolling" on the ListBox:
ScrollViewer.CanContentScroll="False"
Then, you could create a custom Attached Property to specify the vertical offset you want to scroll. Then create a custom Behavior that hooks up to the ListBox's ItemsSource's "ItemsSourceChanged" event, which would fire off an animation that you can define inside the behavior. That should at least be a start. I'm not sure what the specific animation would be...some DoubleAnimation using a calculation of your offset plus new item's height.