【问题标题】:WPF MouseEnter/MouseLeave/MouseMove Event TunnelingWPF MouseEnter/MouseLeave/MouseMove 事件隧道
【发布时间】:2018-01-04 21:45:08
【问题描述】:

我正在尝试制作一个按钮,当鼠标进入按钮时内容会发生变化。 目前,这是我正在使用的代码:

Xaml

<StackPanel Orientation="Vertical">
    <StackPanel Orientation="Horizontal">
        <Button x:Name="One" 
                Content="ONE" 
                Height="50" 
                Width="Auto" 
                MouseEnter="One_OnMouseEnter" 
                MouseLeave="One_OnMouseLeave" />
        <Button x:Name="Two" 
                PreviewMouseMove="Two_OnMouseEnter">
            <Grid>
                <Ellipse Fill="Black" 
                         Height="40" 
                         Width="40" />
                <Label Content="TWO" 
                       Foreground="White" 
                       VerticalAlignment="Center" 
                       HorizontalAlignment="Center" />
            </Grid>
        </Button>
    </StackPanel>
</StackPanel>

C# 代码隐藏文件

private void One_OnMouseEnter(object sender, MouseEventArgs e)
{
    Button b = sender as Button;
    if (b != null)
    {
        b.Foreground = Brushes.Purple;
        b.FontSize = 24;
    }
}

private void One_OnMouseLeave(object sender, MouseEventArgs e)
{
    Button b = sender as Button;
    if (b != null)
    {
        b.Foreground = Brushes.Black;
        b.FontSize = 12;
    }
}

private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{

    Ellipse el = sender as Ellipse;
    if (el != null)
    {
        el.Height = 60;
        el.Width = 60;
        el.Fill = Brushes.White;
    }

    Label l = sender as Label;
    if (l != null)
    {
        l.Foreground = Brushes.Black;
    }

    Grid g = sender as Grid;
    if (g != null)
    {
        g.Height = 200;
        g.Width = 200;
    }
}

那里的第一个按钮按预期工作。 当鼠标移到“One”按钮上时,文本内容会按预期更改。文字颜色变为紫色,字号变大。

我正在尝试用第二个按钮做类似的事情。增加椭圆的大小和改变颜色,改变标签的颜色,改变网格的大小。

问题是第二个按钮似乎没有按预期响应。我尝试使用PreviewMouseMove,据我所知使用隧道路由策略,它应该在按钮的子元素上触发。我使用断点检查,事件似乎只在发送者是按钮时触发。

我的问题是:为什么没有在孩子身上引发事件,因为我已经读到隧道路由策略应该起作用,我可以做些什么来解决它?

此外,MouseEnter 和 MouseLeave 事件似乎遵循 Bubbling 路由策略,但行为更类似于我想要做的。我可以强制它使用隧道路由策略吗?


编辑:

为了进一步说明本项目的目标:

我打算做的是对 WPF 中的事件隧道有一个更完整的了解。

今天早上,我更仔细地检查了这本书,并找到了一种方法,当鼠标进入每个特定孩子的区域时,它就可以完成这项工作,这是一个进步。

这是 C# 代码隐藏文件中的新代码:

private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{
    Ellipse el = e.OriginalSource as Ellipse;
    if (el != null)
    {
        el.Height = 60;
        el.Width = 60;
        el.Fill = Brushes.Orange;
    }
    TextBlock t = e.OriginalSource as TextBlock;
    if (t != null)
    {
        t.Foreground = Brushes.Blue;
    }
    Grid g = e.OriginalSource as Grid;
    if (g != null)
    {
        g.Height = 200;
        g.Width = 200;
    }
}

此代码的不同之处在于它在方法签名中使用MouseEventArgs e 对象,将e.OriginalSource 转换为对象类型,而不是转换sender 对象。

此外,此代码使用 XAML 文件中的 PreviewMouseMove 事件调用来调用:

<Button x:Name="Two" PreviewMouseMove="Two_OnMouseEnter">

这似乎只允许在鼠标进入该区域时更改内容,但在鼠标离开该区域时不允许更改。这让我回到了我最初的问题的一部分:我可以强制 MouseEnter 和 MouseLeave 遵循隧道路由策略吗?

【问题讨论】:

  • 我的建议是在单独的文件 - 资源字典或其他文件中使用样式触发器
  • 我试图通过这个项目完成的是对 WPF 中的事件隧道有一个基本的了解。尽管样式触发器可能会为程序产生所需的结果,但在这种特殊情况下,我想坚持尝试使用事件隧道。

标签: c# wpf


【解决方案1】:

它没有做任何事情,因为事件来自Button(因此是sender)。

你正在尝试做这样的事情:

private void Two_OnMouseEnter(object sender, MouseEventArgs e)
{
    Button b = sender as Button;
    Grid g = b.Content as Grid;
    Ellipse el = g.Children[0] as Ellipse;
    Label l = g.Children[1] as Label;

    g.Height = 200;
    g.Width = 200;

    el.Height = 60;
    el.Width = 60;
    el.Fill = Brushes.White;

    l.Foreground = Brushes.Black;
}

更新

为了更详细地解释隧道策略,请考虑以下示例(为方便起见,我删除了“One”的事件处理程序):

<StackPanel Orientation="Vertical">
    <StackPanel Orientation="Horizontal">
        <Button x:Name="One" Content="ONE" Height="50" Width="Auto" />
        <Button x:Name="Two">
            <Grid PreviewMouseMove="Grid_PreviewMouseMove">
                <Ellipse Fill="Black" Height="40" Width="40" PreviewMouseMove="Ellipse_PreviewMouseMove" />
                <Label Content="TWO" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"
                       PreviewMouseMove="Label_PreviewMouseMove"/>
            </Grid>
        </Button>
    </StackPanel>
</StackPanel>

在代码隐藏中:

private void Grid_PreviewMouseMove(object sender, MouseEventArgs e)
{
    Grid g = sender as Grid;

    if (g != null)
    {
        g.Height = 200;
        g.Width = 200;
    }

    System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}

private void Ellipse_PreviewMouseMove(object sender, MouseEventArgs e)
{
    Ellipse el = sender as Ellipse;

    if (el != null)
    {
        el.Height = 60;
        el.Width = 60;
        el.Fill = Brushes.White;
    }

    System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}

private void Label_PreviewMouseMove(object sender, MouseEventArgs e)
{
    Label l = sender as Label;

    if (l != null)
    {
        l.Foreground = Brushes.Black;
    }

    System.Diagnostics.Debug.WriteLine("Sender: " + sender.GetType() + "; Source: " + e.Source.GetType());
}

这些方法理论上可以重构为一种方法,应用一些逻辑来检测sender 的Type,但它仍然会在XAML 中出现3 次。

需要注意的一点是,Grid 不会触发自己的事件,除非您将其设置为Background 属性(例如,设置为Transparent),尽管当Ellipse 和@987654332 时它仍然会触发@火。

要注意的另一件事是,当Label 触发事件时,即当您将鼠标移到Label 上时,不会调用Ellipse 的事件处理程序。这是因为隧道策略在逻辑上沿树向下进行,而不是在视觉上。如果您查看“输出”窗口中消息中的“来源:”部分,您就会明白我的意思。冒泡事件也是如此。

作为一般观察,这种策略的问题是,当事件从不同的元素触发时,您的控件并没有真正作为一个单一的Button 运行,这似乎是意图。如果您缓慢移动鼠标,您会注意到Ellipse 先触发,然后是Label。

没有任何PreviewMouseEnter 或PreviewMouseLeave 事件,因此无论如何都不能使用隧道。

【讨论】:

  • 这里不解释隧道路由策略。根据隧道路由策略的定义:“事件首先在根上引发,然后在树下的每个元素上引发,直到到达源元素(或直到处理程序通过将事件标记为已处理来停止隧道)。 " source 是“逻辑树中最初引发事件的元素”。
  • 此过程似乎也只适用于非常特定的逻辑树,这显然与 WPF 的使用方式背道而驰。
  • 隧道策略意味着如果您将鼠标移到Ellipse 上,事件将首先在Button 上触发(或尝试),然后是Grid,等等,但要做到这一点,您需要在每个元素上定义事件处理程序。
  • 所以你是在告诉我,当鼠标指针进入椭圆所在的空间时,事件处理方法应该以椭圆作为发送者触发?如果这就是你的意思,这不是我在当前代码中观察到的。
  • 嗯,这取决于你想做什么,我只是在模仿你想要做的事情。就个人而言,我会接受@GlacialMan 的建议并研究触发器,如果​​您只想在鼠标悬停时更改Button 的外观?
猜你喜欢
  • 1970-01-01
  • 2016-04-03
  • 1970-01-01
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多