简体   繁体   中英

c# unsubscribe anonym method with extra parameters

I add images in PictureBox to a TableLayoutPanel and writing text on images with anonym methods, like this:

    private void AddPictureWithText(string text, int textX, int textY, int col, int row)
    {
        var picBox = new PictureBox()
        {
            Image = Properties.Resources.ProgressStage_LT_GRAY,
            SizeMode = PictureBoxSizeMode.Zoom,
            Dock = DockStyle.Fill,
        };
        picBox.Paint += (sender, e) => { picPaint(sender, e, text, textX, textY); };
        tableLayoutPanel1.Controls.Add(picBox, col, row);
    }

    private void picPaint(object sender, PaintEventArgs e, string text, int textPosX, int textPosY)
    {
        using (Font myFont = new Font("Arial", 12))
        {
            e.Graphics.DrawString(text, myFont, Brushes.White, new Point(textPosX, textPosY));
        }
    }

I need anonym method because I have to add extra parameters to Paint event .

How can I unsubscribe from this event?

You subscribed an anonymous method then to unsubscribe you need a reference to that method (which you have not). To make it more complicate it's also a closure then you can't simply move it to a plain class method.

First and most easy workaround (don't do it) is to store that delegate in the Control.Tag property. You can have a local variable or, in C# 7, a local function:

private void AddPictureWithText(string text, int textX, int textY, int col, int row)
{
    var picBox = new PictureBox
    {
        Image = Properties.Resources.ProgressStage_LT_GRAY,
        SizeMode = PictureBoxSizeMode.Zoom,
        Dock = DockStyle.Fill,
        Tag = PaintPictureBox
    };

    picBox.Paint += PaintPictureBox;
    tableLayoutPanel1.Controls.Add(picBox, col, row);

    void PaintPictureBox(object sender, PaintEventArgs e)
        => picPaint(sender, e, text, textX, textY);
} 

To remove it you just need to pick PictureBox.Tag , cast it to PaintEventHandler and remove it:

pbox.Paint -= (PaintEventHandler)pbox.Tag;

Don't do it. It's just a workaround for a wrong approach to the problem.

Let's do it in steps: first declare a plain PaintEventHandler :

private void PaintPictureBox(object sender, PaintEventArgs e)
{
}

You then need some parameters, the best thing is probably to keep track of what you have to draw using a model but let's discuss about it later. For now you can add parameters you need to Control.Tag :

    var picBox = new PictureBox
    {
        Image = Properties.Resources.ProgressStage_LT_GRAY,
        SizeMode = PictureBoxSizeMode.Zoom,
        Dock = DockStyle.Fill,
        Tag = new PaintModel { Text = text, Locaiton = new Point(textx, texty) }
    };

Inside PaintPictureBox :

var data = (PaintModel)((Control)sender).Tag;

And you can access data.Text and data.Location . Don't forget to declare the required PaintModel class/struct with the relevant properties (or to use named tuples). If you do not need any other property you may directly put the Point in the Tag property and use the hidden PictureBox.Text property for the title (in this case you, obviously, do not need PaintModel class/struct.)

Don't do it . This is just a slightly better approach because we're still messing with responsibilities (someone else is responsible to paint text in PictureBox instead of itself.) What's next? Let's introduce a custom control:

sealed class PictureBoxWithText : PictureBox
{
    public Point TextLocation { get; set; }

    public override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);

        using (Font myFont = new Font("Arial", 12))
            pe.Graphics.DrawString(Text, myFont, Brushes.White, TextLocation);
    }
}

Your AddPictureWithText() will then be:

var picBox = new PictureBoxWithText
{
    Text = text,
    TextLocation = new Point(textx, texty),
    Image = Properties.Resources.ProgressStage_LT_GRAY,
    SizeMode = PictureBoxSizeMode.Zoom,
    Dock = DockStyle.Fill,
};

tableLayoutPanel1.Controls.Add(picBox, col, row);

Slightly better , we still have something to improve: we're creating (and correctly disposing) a Font object for each paint operation. It's slow and it consumes resources. Make it a private field:

sealed class PictureBoxWithText : PictureBox
{
    private readonly Font _textFont = new Font("Arial", 12);

    protected override Dispose(bool disposing)
    {
        try
        {
            if (disposing)
                _textFont?.Dispose();
        }
        finally
        {
            base.Dispose(disposing);
        }            
    }

    // Existing implementation
}

If font is fixed then you may move it to a static readonly field (no need to overrride Dispose(bool) then).

Please remember that it's not the perfect or the always-use-this solution, if you're doing this to display a lot of pictures+text then adding 100+ controls will seriously hit performance, in that case you'd better keep a list of objects you're displaying and to perform paint accordingly (dropping the TableLayoutPanel and the PictureBox all together.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM