将父元素的鼠标事件应用于子元素

5

我正在制作一个小型的Windows表单应用程序。
在其中我有一个PictureBox(父级)和一个Label(子级)。

父级的鼠标事件完美地工作,但是由子元素生成的鼠标事件没有反映在父级上。光标也会恢复到默认状态(箭头)。

是否可能将由子控件生成的事件(例如MouseEnter事件)传递给父控件?

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Card.MouseEnter += new EventHandler(Card_MouseEnter);
        Card.MouseLeave += new EventHandler(Card_MouseLeave);
        Card.MouseDown += new MouseEventHandler(this.Card_MouseDown);
        Card.MouseUp += new MouseEventHandler(this.Card_MouseUp);
    }

    void Card_MouseLeave(object sender, EventArgs e)
    {
        this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_bg));
        this.Rename("Running!");
    }

    void Card_MouseEnter(object sender, EventArgs e)
    {
        this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_hover_bg));
    }

    private void Card_MouseDown(object sender, EventArgs e)
    {
        this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_click_bg));
    }

    private void Card_MouseUp(object sender, EventArgs e)
    {
        this.Card.BackgroundImage = ((System.Drawing.Image)(Properties.Resources.card_hover_bg));
        this.Rename("Please Wait...");
    }

    private void CardName_MouseDown(object sender, MouseEventArgs e)
    {
        
    }

    void Rename(string args)
    {
        this.CardName.Text = args;
    }

    private void CardName_Click(object sender, EventArgs e)
    {

    }
}

enter image description here

elements in designer

  This is what I have        And this is what I want to achieve

            This is what I have                       This is what I want to achieve
    

第一个动画代表我现在拥有的,第二个是我需要实现的 :)

开发一个包含PictureBox和Label的USERCONTROL。然后,您可以定义自己的CUSTOM EVENTS,只有在您想要它们被触发时才会触发(而不是使用预设的MouseLeave等事件)。当PictureBox接收到MouseLeave事件时,您可以检查光标位置,以查看它是否仍在PictureBox的范围内(这意味着它进入了Label)。仅当光标实际上在MouseLeave事件中超出PictureBox时,才触发您的自定义“离开”事件。 - Idle_Mind
你尝试在标签的MouseEnter事件中调用Card_MouseEnter(Card, e);了吗?顺便说一下,当鼠标指针进入PictureBox(或UserControl或其他特定区域)时,你可以简单地绘制该文本并更改光标或执行其他操作。你可以使用Rectangle.Contains(Point)来实现这一点。 - Jimi
如果您在设计器中放置它,则可以使用以下代码在Load()事件中将其设置为父级,以使其保持在相同的位置:(1)Point pt = this.PointToScreen(label1.Location);(2) pictureBox1.Controls.Add(label1);(3)label1.Location = pictureBox1.PointToClient(pt); - Idle_Mind
我尝试使用Card.Controls.Add(CardName);,但它消失了... 我也尝试了Idle_Mind的想法,没有错误,但是仍然一样,没有改变。 - Tiffany
在设置了父级之后,只需编写 CardName.Location = new Point(0, Card.Height - CardName.Height); CardName.BringToFront();(或者Idle_Mind发布的内容),重定位标签,因为其位置现在相对于新的父级。通常最好也将子控件 BringToFront(),因为PictureBox不是ContainerControl。 - Jimi
显示剩余5条评论
2个回答

2
当我使用pictureBox1.Controls.Add(label1)时,label1会消失,我尝试使用bring to front和更改颜色,但无法实现。如果你有任何想法,请在我提供的代码中向我展示,以便我能够理解。再次感谢大家:)
您可以在窗体的Load()事件中使用以下代码:
private void Form1_Load(object sender, EventArgs e)
{
    Point pt = CardName.Parent.PointToScreen(CardName.Location);
    Card.Controls.Add(CardName);
    CardName.Location = Card.PointToClient(pt);
}

这将保持标签在原位置不变,但将picturebox作为其父级。

不确定你哪里出错了。这里有一个示例展示它的效果。PictureBox (Card) 和 Label (CardName) 都在 Panel (panel1) 中。单击 button2 切换 Card 的可见性。单击 button1 使 Card 成为 CardName 的父级。可以看到,起初只有 Card 切换可见性,但在单击 button1 并设置 Parent 后,两者一起切换可见性,因为 CardName 是 Card 的子级(它还更改了其 BackColor 以匹配其新 Parent):

enter image description here

代码:

public partial class Form1 : Form
{

    private void button1_Click(object sender, EventArgs e)
    {
        Point pt = CardName.Parent.PointToScreen(CardName.Location);
        Card.Controls.Add(CardName);
        CardName.Location = Card.PointToClient(pt);
    }

    private void button2_Click(object sender, EventArgs e)
    {
        Card.Visible = !Card.Visible;
    }

}

当我将鼠标移到标签上时,面板认为鼠标已离开并触发MouseLeave事件。
以下是如何判断光标是否实际离开了Panel的边界,而不仅仅是进入Panel中的子控件的方法:
private void panel1_MouseEnter(object sender, EventArgs e)
{
    panel1.BackColor = Color.Red;
}

private void panel1_MouseLeave(object sender, EventArgs e)
{
    Point pt = panel1.PointToClient(Cursor.Position);
    if (!panel1.ClientRectangle.Contains(pt))
    {
        // we only get in here when the cursor leaves the BOUNDS of panel1
        panel1.BackColor = Control.DefaultBackColor;
    }            
}

我之前在你的评论中尝试过了,但它真的不起作用 :'( - Tiffany
我已经发布了,请查看。 - Tiffany
抱歉,我按照您的要求进行了操作,但是它并没有起作用。也许我的解释有些混淆,因此我在描述中添加了gif,请查看,也许这样会更清晰易懂。 - Tiffany
好的,你说得对,将PictureBox改成Panel后,标签自动分配给了Panel,并且Cursors.Hand正在工作,但悬停效果仍然是个问题。 :'( - Tiffany
所以您在面板中有标签,光标是固定的。请再次解释“悬停”问题。 - Idle_Mind
显示剩余7条评论

2
首先,您应该建立一个UserControl作为所有对象的容器:这将使一切变得更简单(我在这里使用的实际上是一个UserControl,修改以符合您当前的设置)。
当除PictureBox以外的控件与之交互时,您可以决定是否要在主控件上触发类似的操作,或者根据生成的事件执行不同的操作。
▶ 当鼠标指针进入您的组合控件时,您想要更改默认光标:然后,当其中一个标签引发Enter事件时,调用处理此事件的主控件的方法。事件处理程序是一个方法,您可以调用它。
▶ 当单击标签时,您不想触发主控件的相关操作:在这种情况下,没有什么可做的,只需处理此事件并执行所需的操作。
▶ 标签应为主控件的子级。您正在使用PictureBox,它不是ContainerControl。您仍然可以向其添加子控件。您需要在代码中执行此操作,因为-如上所述-PictureBox不设计为托管控件,因此您无法在其中放置一个控件:您放置的控件将成为承载PictureBox的容器(即您的窗体)的父控件。
当您在代码中设置父级时,需要记住子控件的位置相对于旧父级,因此您必须重新定义其位置。
例如:PictureBox.Bounds = (100, 100, 100, 200) / Label.Bounds = (100, 250, 100, 50) 当PictureBox成为标签的父级时,Label.Location仍然是(100, 250):因此,现在它将被隐藏,因为它在新父容器中的可见边界之外。您需要根据新主机重新定位它:它的新位置应该是(0, 150),以保持先前的相对位置。
PictureBox.Control.Add(Label);
//[...]
Label.Location = new Point(Label.Left - PictureBox.Left, Label.Top - PictureBox.Top);
=> Label.Location = (100 - 100, 250 - 100) => (0, 150)

或者,水平居中:

Label.Location = new Point((PictureBox.Width - Label.Width) / 2, Label.Top - PictureBox.Top);
=> Label.Location = ((100 - 100) / 2, 250 - 100) => (0, 150) // <- Since both have the same Width

或者,使用相对于屏幕的位置:
var p = Label.PointToScreen(Point.Empty);  // Relative to the ClientRectangle (Top/Left = (0, 0))
PictureBox.Controls.Add(Label);
Label.Location = PictureBox.PointToClient(p);

无论如何,在添加新的子控件后,请调用BringToFront(),以确保新的子控件位于最上层,并锚定该控件,使其保持位置并将其宽度绑定到父控件的宽度:

Label.BringToFront();
Label.Anchor = AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right;

现在,假设您想在鼠标进入您的组合控件时将光标更改为 Cursors.Hand,并在离开时重置为默认值:

▶ 您希望无论如何都更改光标形状。
▶ 您希望在单击PictureBox和单击其中一个标签时生成不同的操作。
▶ 单击每个标签时均可以有不同的操作。

UserControl Mixed Event Handlers

在可视化示例中,位于PictureBox上方的标签名为lblTitle,位于底部的PictureBox内的标签名为lblFooter.
PictureBox的名称为ImageView.

设置处理程序:

注意:对于用户控件,例如与MouseEnter相关的事件处理会发生变化:

// The Parent's MouseEnter calls OnMouseEnter
protected override void OnMouseEnter(EventArgs e)
{
    base.OnMouseEnter(e);
    this.Cursor = Cursors.Hand;
}

// Child Controls just call the same method
private void Labels_MouseEnter(object sender, EventArgs e) => OnMouseEnter(e);

public Form1()
{
    InitializeComponent();

        Point p = lblFooter.PointToScreen(Point.Empty); 
        ImageView.Controls.Add(lblFooter);
        lblFooter.Location = ImageView.PointToClient(p);

        ImageView_MouseEnter += ImageView_MouseEnter;
        ImageView_MouseLeave += ImageView_MouseLeave;
        // Not added in the code here, do whatever is needed with this handler
        ImageView_Click += ImageView_Click;

        lblFooter.MouseEnter += Labels_MouseEnter;
        lblFooter.MouseLeave += Labels_MouseLeave;
        lblFooter.MouseClick += lblFooter_MouseClick;

        lblTitle.MouseEnter += Labels_MouseEnter;
        lblTitle.MouseLeave += Labels_MouseLeave;
        lblTitle.MouseDown += lblTitle_MouseDown;
        lblTitle.MouseUp += lblTitle_MouseUp;
}

private void ImageView_MouseEnter(object sender, EventArgs e) => this.Cursor = Cursors.Hand;

private void ImageView_MouseLeave(object sender, EventArgs e) => this.Cursor = Cursors.Default;

private void Labels_MouseEnter(object sender, EventArgs e)
{
    ImageView_MouseEnter(ImageView, e);
    // [...]
    // Do stuff related to the Labels Enter event
}

private void Labels_MouseLeave(object sender, EventArgs e) {
    ImageView_MouseLeave(ImageView, e);
}

private void lblTitle_MouseDown(object sender, MouseEventArgs e) {
    // Perform actions when the Mouse button is held down lblTitle
}

private void lblTitle_MouseUp(object sender, MouseEventArgs e) {
    // Perform actions when the Mouse button is released
}

private void lblFooter_MouseClick(object sender, MouseEventArgs e) {
    // Perform actions on a Mouse Click event on lblFooter
}

非常感谢Jimi的回答。由于你的回答,我已经将整个项目重建为UserControl,这确实是正确的方法。但是,由于Idle_Mind的答案与我在此处发布的旧代码完全相符,因此应选择他的答案作为最终答案。再次感谢你的帮助 :) - Tiffany
当然,很高兴能帮到你 :) - Jimi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接