我似乎已经接受了一个"不清楚我在问什么"投票。我想自定义绘制一个组合框样式控件。弹出打开部分需要在控件本身的范围之外绘制。我无法使用组合框 - 想想类似于Word功能区中的图库控件。
我想到了两种方法:
后者也允许下拉逃脱窗口的边界,这可能是有用的,但并非绝对必要。
对此有没有其他方法,您认为哪种方法最好?
感谢。
原始问题:
我正在为一个小项目编写基于winforms的自定义绘图UI库。一切都进展顺利,但我有一个轻微的结构问题,下降留下Graphics
对象的边界。
大多数控件都是使用纯自定义绘制和重绘事件模型完成的,但整个界面使用winforms Dock
,Width
,Height
等进行布局。
我添加了一个下拉列表,但显然当它的下拉部分的边界超出了布局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来演示)
我还记得小窗口强迫ComboBox超出窗口的边界。
它上面的投影看起来很像CS_DROPSHADOW
CreateParams
对我来说 - 我可以使用NativeWindow
子类来处理这个吗?
答案 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;
}
}
我的快速测试平台应用程序的最终结果:
通过以这种方式接近它,我也可以使下拉列表逃脱窗口的边界:
我现在唯一的问题 - 当你打开每个窗框时,窗框会失去焦点,这有点刺耳。我可以通过覆盖ShowWithoutActivation
来返回true,但是LostFocus
处理程序不起作用。
现在更多的是烦恼,但任何修复的建议非常受欢迎!
答案 1 :(得分:0)
我前几天看了这个,因为我遇到了同样的情况,但我选择以不同的方式接近它,也许是一个更复杂的解决方案。在下图中,在TestingForm中有一个SplitContainer。在右侧面板(背景橙色)中有两个控件,一个是TextBox,另一个是Panel(背景颜色海蓝宝石)。在海蓝宝石面板内部是一个停靠的第二个分割面板控件,在左侧包含一个标签,我的&#34; CJL_PanelCtrl&#34;在右边。显然,单击箭头将打开控件&#34; down&#34;重叠任何其他控件。
我的解决方案是创建一个面板并将其置于不受限制的父控件更改中。在此示例中,它将是第一个SplitContainer的右侧面板(橙色面板)。在这种情况下,如果要重新调整第一个SplitContiner,它将正确移动面板的位置。
请注意,面板的父颜色为橙色。然后,以下代码完成此操作。
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事件中添加了一个事件处理程序,如果(在我的测试中就是这种情况)控件被左右固定,它会调整大小小组正确。
干杯