自定义绘制下拉面板外控制边界

时间:2015-01-25 22:25:25

标签: c# winforms graphics gdi+ gdi

我似乎已经接受了一个"不清楚我在问什么"投票。我想自定义绘制一个组合框样式控件。弹出打开部分需要在控件本身的范围之外绘制。我无法使用组合框 - 想想类似于Word功能区中的图库控件。

我想到了两种方法:

  • 将弹出式打开面板返回到要渲染的表单。
  • 使用无边框,无框架表格或NativeWindow。

后者也允许下拉逃脱窗口的边界,这可能是有用的,但并非绝对必要。

对此有没有其他方法,您认为哪种方法最好?

感谢。


原始问题:

我正在为一个小项目编写基于winforms的自定义绘图UI库。一切都进展顺利,但我有一个轻微的结构问题,下降留下Graphics对象的边界。

大多数控件都是使用纯自定义绘制和重绘事件模型完成的,但整个界面使用winforms DockWidthHeight等进行布局。

我添加了一个下拉列表,但显然当它的下拉部分的边界超出了布局Panel的图形对象的边界时,它会被切断。

(我原本预计会在SO上找到与此类似的东西,但是无法管理。)

我通过让表单控制下拉覆盖图的绘制来解决这个问题,但是使用自定义鼠标处理程序和其他所有内容,表单开始感到负担过重。

我试图存储对Graphics个对象的引用,但发现在OnPaint之外使用它们是......气质。

以下是当前模型的简化代码示例。此代码不会以任何有用的方式运行,但会显示用于显示叠加层的方法。

public interface IDropDownOverlay
{
    DropDown DropDown { get; }

    /// <summary>can only link to a single form at once - not a problem.</summary>
    DropDownDrawForm Form { get; set; }

    void MouseUpdate(MouseEventArgs e);

    void Render(Graphics gfx);

    void Show();
}

public class DropDown
{
    private DropDownOverlay overlay;
}

public class DropDownDrawForm : Form
{
    /* lots of other things... */

    private List<IDropDownOverlay> overlays;

    public void HideOverlay(IDropDownOverlay overlay)
    {
        if (this.overlays.Contains(overlay))
        {
            this.overlays.Remove(overlay);
            this.Invalidate();
        }
    }

    public void ShowOverlay(IDropDownOverlay overlay)
    {
        if (!this.overlays.Contains(overlay))
        {
            overlay.Form = this;
            this.overlays.Add(overlay);
            this.Invalidate();
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        foreach (IDropDownOverlay overlay in this.overlays)
        {
            overlay.Render(e.Graphics);
        }
    }

    private void MouseUpdate(MouseEventArgs e)
    {
        foreach (IDropDownOverlay overlay in this.overlays)
        {
            overlay.MouseUpdate(e);
        }
    }
}

public class DropDownOverlay : IDropDownOverlay
{
    public DropDown DropDown { get; }

    public DropDownDrawForm Form { get; set; }

    public void Hide()
    {
        this.Form.HideOverlay(this);
    }

    public void MouseUpdate(MouseEventArgs e)
    {
        // Informs the form to redraw the area of the control.

        if (stateChanged)
        {
            this.Invalidate(); // (method to invalidate just this area)
        }
    }

    public void Show()
    {
        this.Form.ShowOverlay(this);
    }

    public void Render(Graphics gfx)
    {
    }
}

显然这里缺少很多位,但它应该显示我至少使用的方法。

是否有任何建议可以阻止我在表单之间来回传递?

由于


更新

只是为了绝对清楚,问题在于绘制&#34; popup&#34;下拉列表部分不是下拉列表本身。 (这里使用ComboBox来演示)

DropDown Control Areas

我还记得小窗口强迫ComboBox超出窗口的边界。

ComboBox outside window bounds

它上面的投影看起来很像CS_DROPSHADOW CreateParams对我来说 - 我可以使用NativeWindow子类来处理这个吗?

2 个答案:

答案 0 :(得分:1)

我想我已经确定了第二个选项,即使用第二个表单来显示下拉列表面板。我使用了扩展的Form类而不是NativeWindow。我以为我应该分享结果,以防其他人尝试同样的事情并发现这一点。

选择下拉列表后,我使用PointToScreen设置表单以获取坐标。它还具有以下属性集:

            this.ShowIcon = false;
            this.ControlBox = false;
            this.MinimizeBox = false;
            this.MaximizeBox = false;
            this.ShowInTaskbar = false;
            this.FormBorderStyle = FormBorderStyle.None;

只是为了确保它不会出现在任何地方。我还添加了以下事件处理程序:

            this.LostFocus += delegate
            {
                this.dropdown.BlockReopen(200);
                this.dropdown.Close();
            };

这意味着它会在焦点丢失后立即关闭,并且还会调用一种方法来阻止下拉列表重新打开200毫秒。我对此并不十分满意,但解决了很多问题,它可能会停留一段时间。我还通过覆盖CreateParams来添加阴影:

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams createParams = base.CreateParams;
                createParams.ClassStyle |= Win32Message.CS_DROPSHADOW;
                return createParams;
            }
        }

我的快速测试平台应用程序的最终结果:

Ribbon Dropdown Prototype

通过以这种方式接近它,我也可以使下拉列表逃脱窗口的边界:

Ribbon Dropdown Prototype

我现在唯一的问题 - 当你打开每个窗框时,窗框会失去焦点,这有点刺耳。我可以通过覆盖ShowWithoutActivation来返回true,但是LostFocus处理程序不起作用。

现在更多的是烦恼,但任何修复的建议非常受欢迎!

答案 1 :(得分:0)

我前几天看了这个,因为我遇到了同样的情况,但我选择以不同的方式接近它,也许是一个更复杂的解决方案。在下图中,在TestingForm中有一个SplitContainer。在右侧面板(背景橙色)中有两个控件,一个是TextBox,另一个是Panel(背景颜色海蓝宝石)。在海蓝宝石面板内部是一个停靠的第二个分割面板控件,在左侧包含一个标签,我的&#34; CJL_PanelCtrl&#34;在右边。显然,单击箭头将打开控件&#34; down&#34;重叠任何其他控件。

我的解决方案是创建一个面板并将其置于不受限制的父控件更改中。在此示例中,它将是第一个SplitContainer的右侧面板(橙色面板)。在这种情况下,如果要重新调整第一个SplitContiner,它将正确移动面板的位置。

enter image description here

请注意,面板的父颜色为橙色。然后,以下代码完成此操作。

CJL_DropPanelCtrl里面有两个控件,一个按钮_btn,根据状态改变图像,还有一个TextBox,_TB

public partial class CJL_DropPanelCtrl : UserControl
{
    internal bool IsDropped { get; set; }       // true if the panel is visible

    internal Panel DropPanel { get; private set; }  // the panel make the public set to set your own panel as needed


    /// <summary>
    /// The Control where the DropPanel will be in the Z-order chain
    /// </summary>
    internal Control PanelParent
    { get { return DropPanel.Parent; }
      set 
        {   
          DropPanel.Parent = value;
          SetLocation();
        }
    }

    public CJL_DropPanelCtrl()
    {
        InitializeComponent();
        IsDropped = false;

        DropPanel = new Panel();
        DropPanel.BorderStyle = BorderStyle.FixedSingle;
        DropPanel.Height = 100;
        DropPanel.Visible = false;  
    }

    private void OnBtnClick(object sender, EventArgs e)
    {
        IsDropped = !IsDropped;
        _btn.Image = IsDropped ? Properties.Resources.DnPointer : Properties.Resources.RtPointer;
        DropPanel.Visible = IsDropped;
        DropPanel.BringToFront();   
    }

    internal void SetLocation()
    {
        // here we go up the chain of controls to determine where the location of the panel is
        // to be placed.
        Control c = _TB;
        Point offset = new Point(1, _TB.Height+2);
        while (c != DropPanel.Parent)
        {
            offset.X += c.Location.X;
            offset.Y += c.Location.Y;
            c = c.Parent;
        }
        DropPanel.Location = offset;


    }

    private void OnTBSizeChanged(object sender, EventArgs e)
    {
        DropPanel.Width = _TB.Width;
    }

}

在我的TestingForm代码中看起来像这样。请注意,我设置了PanelParent属性,并为第二个SplitContainer拆分器移动添加了一个EventHandler。

public partial class TestingForm : Form
{

    public TestingForm()
    {
        InitializeComponent();
    }

    private void OnLoad(object sender, EventArgs e)
    {
        cjL_DropPanelCtrl1.PanelParent = splitContainer1.Panel2;
    }

    private void OnSplitter2Moved(object sender, SplitterEventArgs e)
    {
        cjL_DropPanelCtrl1.SetLocation();
    }

}

唯一需要注意的是我在CJL_DropPanelCtrl的TextBox的SizeChanged事件中添加了一个事件处理程序,如果(在我的测试中就是这种情况)控件被左右固定,它会调整大小小组正确。

干杯