Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Baseline snaplines in custom Winforms controls

I have a custom user control with a textbox on it and I'd like to expose the baseline (of the text in the textbox) snapline outside of the custom control. I know that you create a designer (inherited from ControlDesigner) and override SnapLines to get access to the snaplines, but I'm wondering how to get the text baseline of a control that I have exposed by my custom user control.

like image 789
Mike Avatar asked Sep 18 '08 15:09

Mike


3 Answers

As an update to the Miral's answer.. here are a few of the "missing steps", for someone new that's looking how to do this. :) The C# code above is almost 'drop-in' ready, with the exception of changing a few of the values to reference the UserControl that will be modified.

Possible References Needed:
System.Design (@robyaw)

Usings needed:

using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;

On your UserControl you need the following Attribute:

[Designer(typeof(MyCustomDesigner))]

Then you need a "designer" class that will have the SnapLines override:

private class MyCustomerDesigner : ControlDesigner {
  public override IList SnapLines {
    get {
     /* Code from above */
    IList snapLines = base.SnapLines;

    // *** This will need to be modified to match your user control
    MyControl control = Control as MyControl;
    if (control == null) { return snapLines; }

    // *** This will need to be modified to match the item in your user control
    // This is the control in your UC that you want SnapLines for the entire UC
    IDesigner designer = TypeDescriptor.CreateDesigner(
        control.textBoxValue, typeof(IDesigner));
    if (designer == null) { return snapLines; }

    // *** This will need to be modified to match the item in your user control
    designer.Initialize(control.textBoxValue);

    using (designer)
    {
        ControlDesigner boxDesigner = designer as ControlDesigner;
        if (boxDesigner == null) { return snapLines; }

        foreach (SnapLine line in boxDesigner.SnapLines)
        {
            if (line.SnapLineType == SnapLineType.Baseline)
            {
                // *** This will need to be modified to match the item in your user control
                snapLines.Add(new SnapLine(SnapLineType.Baseline,
                    line.Offset + control.textBoxValue.Top,
                    line.Filter, line.Priority));
                break;
            }
        }
    }

    return snapLines;
}

    }
  }
}
like image 57
Matthew M. Avatar answered Nov 13 '22 22:11

Matthew M.


I just had a similar need, and I solved it like this:

 public override IList SnapLines
{
    get
    {
        IList snapLines = base.SnapLines;

        MyControl control = Control as MyControl;
        if (control == null) { return snapLines; }

        IDesigner designer = TypeDescriptor.CreateDesigner(
            control.textBoxValue, typeof(IDesigner));
        if (designer == null) { return snapLines; }
        designer.Initialize(control.textBoxValue);

        using (designer)
        {
            ControlDesigner boxDesigner = designer as ControlDesigner;
            if (boxDesigner == null) { return snapLines; }

            foreach (SnapLine line in boxDesigner.SnapLines)
            {
                if (line.SnapLineType == SnapLineType.Baseline)
                {
                    snapLines.Add(new SnapLine(SnapLineType.Baseline,
                        line.Offset + control.textBoxValue.Top,
                        line.Filter, line.Priority));
                    break;
                }
            }
        }

        return snapLines;
    }
}

This way it's actually creating a temporary sub-designer for the subcontrol in order to find out where the "real" baseline snapline is.

This seemed reasonably performant in testing, but if perf becomes a concern (and if the internal textbox doesn't move) then most of this code can be extracted to the Initialize method.

This also assumes that the textbox is a direct child of the UserControl. If there are other layout-affecting controls in the way then the offset calculation becomes a bit more complicated.

like image 33
Miral Avatar answered Nov 13 '22 22:11

Miral


Thanks to all those for the help. This was a tough one to swallow. The thought having a private sub-class in every UserControl wasn't very palatable.

I came up with this base class to help out..

[Designer(typeof(UserControlSnapLineDesigner))]
public class UserControlBase : UserControl
{
    protected virtual Control SnapLineControl { get { return null; } }

    private class UserControlSnapLineDesigner : ControlDesigner
    {
        public override IList SnapLines
        {
            get
            {
                IList snapLines = base.SnapLines;

                Control targetControl = (this.Control as UserControlBase).SnapLineControl;

                if (targetControl == null)
                    return snapLines;

                using (ControlDesigner controlDesigner = TypeDescriptor.CreateDesigner(targetControl,
                    typeof(IDesigner)) as ControlDesigner)
                {
                    if (controlDesigner == null)
                        return snapLines;

                    controlDesigner.Initialize(targetControl);

                    foreach (SnapLine line in controlDesigner.SnapLines)
                    {
                        if (line.SnapLineType == SnapLineType.Baseline)
                        {
                            snapLines.Add(new SnapLine(SnapLineType.Baseline, line.Offset + targetControl.Top,
                                line.Filter, line.Priority));
                            break;
                        }
                    }
                }
                return snapLines;
            }
        }
    }
}

Next, derive your UserControl from this base:

public partial class MyControl : UserControlBase
{
    protected override Control SnapLineControl
    {
        get
        {
            return txtTextBox;
        }
    }

    ...

}

Thanks again for posting this.

like image 7
Robert H. Avatar answered Nov 13 '22 23:11

Robert H.