Baseline snaplines in custom Winforms controls

后端 未结 5 1611
余生分开走
余生分开走 2020-12-08 07:19

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

相关标签:
5条回答
  • 2020-12-08 07:26

    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.

    0 讨论(0)
  • 2020-12-08 07:32

    VB.Net Version:
    Note: you have to change the txtDescription to the Textbox or another internal control name that you use. and ctlUserControl to your usercontrol name

    <Designer(GetType(ctlUserControl.MyCustomDesigner))> _
    Partial Public Class ctlUserControl
       '... 
       'Your Usercontrol class specific code
       '... 
        Class MyCustomDesigner
            Inherits ControlDesigner
            Public Overloads Overrides ReadOnly Property SnapLines() As IList
                Get
                    ' Code from above 
    
                    Dim lines As IList = MyBase.SnapLines
    
                    ' *** This will need to be modified to match your user control
                    Dim control__1 As ctlUserControl = TryCast(Me.Control, ctlUserControl)
                    If control__1 Is Nothing Then Return lines
    
                    ' *** 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
                    Dim designer As IDesigner = TypeDescriptor.CreateDesigner(control__1.txtDescription, GetType(IDesigner))
                    If designer Is Nothing Then
                        Return lines
                    End If
    
                    ' *** This will need to be modified to match the item in your user control
                    designer.Initialize(control__1.txtDescription)
    
                    Using designer
                        Dim boxDesigner As ControlDesigner = TryCast(designer, ControlDesigner)
                        If boxDesigner Is Nothing Then
                            Return lines
                        End If
    
                        For Each line As SnapLine In boxDesigner.SnapLines
                            If line.SnapLineType = SnapLineType.Baseline Then
                                ' *** This will need to be modified to match the item in your user control
                                lines.Add(New SnapLine(SnapLineType.Baseline, line.Offset + control__1.txtDescription.Top, line.Filter, line.Priority))
                                Exit For
                            End If
                        Next
                    End Using
    
                    Return lines
                End Get
            End Property
        End Class
    
    End Class
    
    0 讨论(0)
  • 2020-12-08 07:36

    You're on the right track. You will need to override the SnapLines property in your designr and do something like this:

    Public Overrides ReadOnly Property SnapLines() As System.Collections.IList
        Get
            Dim snapLinesList As ArrayList = TryCast(MyBase.SnapLines, ArrayList)
    
            Dim offset As Integer
            Dim ctrl As MyControl = TryCast(Me.Control, MyControl)
            If ctrl IsNot Nothing AndAlso ctrl.TextBox1 IsNot Nothing Then
                offset = ctrl.TextBox1.Bottom - 5
            End If
    
            snapLinesList.Add(New SnapLine(SnapLineType.Baseline, offset, SnapLinePriority.Medium))
    
            Return snapLinesList
    
        End Get
    End Property
    

    In this example the usercontrol contains a textbox. The code adds a new snapline that represents the baseline for the textbox. The important thing is to calculate the offset correctly.

    0 讨论(0)
  • 2020-12-08 07:48

    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.

    0 讨论(0)
  • 2020-12-08 07:50

    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;
    }
    
        }
      }
    }
    
    0 讨论(0)
提交回复
热议问题