我似乎获得了“不清楚我在问什么”的投票。我想自定义绘制一个组合框样式控件。弹出打开部分需要在控件本身的边界之外绘制。我无法使用组合框 - 想想类似于 Word 功能区中的库控件的东西。
我想到了两种方法:
- 将弹出打开的面板传递回表单以进行渲染。
- 使用无边框、无框架 Form 或 NativeWindow。
后者还允许下拉菜单脱离窗口的边界,这可能有用,但不是绝对必要的。
还有其他方法吗?您认为哪种方法最好?
谢谢。
原始问题:
我正在为一个小项目编写一个基于 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)
{
}
}
显然,其中缺少很多内容,但它至少应该显示我正在使用的方法。
有什么建议可以防止我在表单之间来回传递此信息吗?
谢谢
更新:
为了绝对清楚,问题是绘制下拉列表的“弹出”部分,而不是下拉列表本身。 (这里使用ComboBox来演示)
我还记得小窗口会迫使 ComboBox 超出窗口的范围。
对我来说,它上面的阴影看起来很像来自 CreateParams
的 CS_DROPSHADOW
- 我可以使用 NativeWindow
子类来处理这个问题吗?
最佳答案
我想我已经选择了第二个选项,即使用第二个表单来显示下拉面板。我使用了扩展 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
处理程序不起作用。
现在更烦人了,但是非常欢迎任何解决这个问题的建议!
关于c# - 自定义绘制下拉面板在控制范围之外,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28142305/