C# 如何在运行时基于鼠标移动移动窗体上的所有控件
我在winforms应用程序中执行某项任务时遇到了一些小问题 我基本上是尝试在winform上重新创建俯视RTS地图。为了节省内存,并非地图的所有分幅都显示在屏幕上。仅适用于视口中的对象。因此,我尝试允许用户在显示的平铺上执行平移/滚动,以便浏览整个地图 现在,我通过在运行时动态创建和显示GroupBox控件来实现这一点。这些代表瓷砖 我创建了自己的对象来支持所有这些,包括屏幕坐标、行和列信息等 以下是我目前如何用伪代码完成所有这些: 创建窗体、平铺和地图 我创建了一个winforms表单,它是600px X 600px 我创建了一个新的映射,使用一个100个tiles x 100个tiles的列表来测试表单加载,并将其保存到一个变量中 我通过从主列表bool MapTile.isDrawn派生的另一个列表或属性跟踪显示的平铺 每个互动程序在视觉上由100px的GroupBox控件组成 X 100px,使其中[7 X 7]个适合屏幕C# 如何在运行时基于鼠标移动移动窗体上的所有控件,c#,.net,winforms,gdi+,C#,.net,Winforms,Gdi+,我在winforms应用程序中执行某项任务时遇到了一些小问题 我基本上是尝试在winform上重新创建俯视RTS地图。为了节省内存,并非地图的所有分幅都显示在屏幕上。仅适用于视口中的对象。因此,我尝试允许用户在显示的平铺上执行平移/滚动,以便浏览整个地图 现在,我通过在运行时动态创建和显示GroupBox控件来实现这一点。这些代表瓷砖 我创建了自己的对象来支持所有这些,包括屏幕坐标、行和列信息等 以下是我目前如何用伪代码完成所有这些: 创建窗体、平铺和地图 我创建了一个winforms表单,它是
开始时,我在地图中找到中心地图瓦片[50, 50 ],为它创建GROMPPBOX,并将其放置在窗体的中间,
然后,我添加其他必要的平铺/控件,以填充表单中心-3平铺、中心+3上、下、左和右平铺 当然,每个磁贴都会订阅适当的鼠标事件来执行拖动 当用户鼠标拖动平铺时,显示的所有其他平铺跟随/跟随引线,更新所有显示的平铺坐标以匹配拖动的平铺所做的移动 管理显示的磁贴 在拖动/移动GroupBox平铺时,我会执行一项检查,以查看视口外边缘上的平铺是否在其边界内。 例如,如果最左上角平铺的右边缘落在视口左边缘的边界之外,我将删除整个左列平铺,并以编程方式添加整个右列平铺。这同样适用于上、下、左、右各个方向。 到目前为止,只要我不走得太快,这个就行了。。。但是,当我将磁贴拖得太快,超过了外部边缘时,例如:点2 ci dessus将应用的位置,应用程序似乎无法跟上,因为它没有在表单上添加它们应该位于的列或行,以及其他时间,它没有时间删除一行或一列的所有控件,我最终得到的控件仍然在屏幕上,而它们不应该在屏幕上。此时,整个网格/地图处于失衡状态,并停止按预期工作,因为应该在一个边缘触发的事件不存在,或者平铺不存在,或者现在表单上有多个同名控件,并且删除或引用失败 虽然我很清楚winforms并不是为执行密集的GPU/GDI操作而设计的,但您会认为在winforms中这样简单的操作仍然很容易做到吗 我将如何在运行时使其更具响应性?以下是我的整套代码: 表格代码我将使用DataGridView、TableLayoutPanel、GDI+或其他工具创建网格,然后在拖放中,只计算新索引并更新索引,而不移动网格 范例 以下示例显示了如何使用TableLayoutPanel执行此操作: 为单元格指定固定大小 构建网格以填充表单 当窗体调整大小时,重新生成网格 在鼠标下键中,捕获鼠标下点和网格的当前左上角索引 在鼠标移动中,根据鼠标移动计算新索引并更新索引 在面板的单元格绘制中,绘制索引 代码如下:
int topIndex = 0, leftIndex = 0;
int originalLeftIndex = 0, originalTopIndex = 0;
int cellSize = 100;
Point p1;
TableLayoutPanel panel;
void LayoutGrid()
{
panel.SuspendLayout();
var columns = (ClientSize.Width / cellSize) + 1;
var rows = (ClientSize.Height / cellSize) + 1;
panel.RowCount = rows;
panel.ColumnCount = columns;
panel.ColumnStyles.Clear();
panel.RowStyles.Clear();
for (int i = 0; i < columns; i++)
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, cellSize));
for (int i = 0; i < rows; i++)
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, cellSize));
panel.Width = columns * cellSize;
panel.Height = rows * cellSize;
panel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
panel.ResumeLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
panel = new MyGrid();
this.Controls.Add(panel);
LayoutGrid();
panel.MouseDown += Panel_MouseDown;
panel.MouseMove += Panel_MouseMove;
panel.CellPaint += Panel_CellPaint;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (panel != null)
LayoutGrid();
}
private void Panel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
var g = e.Graphics;
TextRenderer.DrawText(g, $"({e.Column + leftIndex}, {e.Row + topIndex})",
panel.Font, e.CellBounds, panel.ForeColor);
}
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var dx = (e.Location.X - p1.X) / cellSize;
var dy = (e.Location.Y - p1.Y) / cellSize;
leftIndex = originalLeftIndex - dx;
topIndex = originalTopIndex - dy;
panel.Invalidate();
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
p1 = e.Location;
originalLeftIndex = leftIndex;
originalTopIndex = topIndex;
}
我将使用DataGridView、TableLayoutPanel、GDI+或其他工具创建网格,然后在拖放中,只计算新索引并更新索引,而不移动网格 范例 以下示例显示了如何使用TableLayoutPanel执行此操作: 为单元格指定固定大小 构建网格以填充表单 当窗体调整大小时,重新生成网格 在鼠标下键中,捕获鼠标下点和网格的当前左上角索引 在鼠标移动中,根据鼠标移动计算新索引并更新索引 在面板的单元格绘制中,绘制索引 代码如下:
int topIndex = 0, leftIndex = 0;
int originalLeftIndex = 0, originalTopIndex = 0;
int cellSize = 100;
Point p1;
TableLayoutPanel panel;
void LayoutGrid()
{
panel.SuspendLayout();
var columns = (ClientSize.Width / cellSize) + 1;
var rows = (ClientSize.Height / cellSize) + 1;
panel.RowCount = rows;
panel.ColumnCount = columns;
panel.ColumnStyles.Clear();
panel.RowStyles.Clear();
for (int i = 0; i < columns; i++)
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, cellSize));
for (int i = 0; i < rows; i++)
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, cellSize));
panel.Width = columns * cellSize;
panel.Height = rows * cellSize;
panel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
panel.ResumeLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
panel = new MyGrid();
this.Controls.Add(panel);
LayoutGrid();
panel.MouseDown += Panel_MouseDown;
panel.MouseMove += Panel_MouseMove;
panel.CellPaint += Panel_CellPaint;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (panel != null)
LayoutGrid();
}
private void Panel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
var g = e.Graphics;
TextRenderer.DrawText(g, $"({e.Column + leftIndex}, {e.Row + topIndex})",
panel.Font, e.CellBounds, panel.ForeColor);
}
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var dx = (e.Location.X - p1.X) / cellSize;
var dy = (e.Location.Y - p1.Y) / cellSize;
leftIndex = originalLeftIndex - dx;
topIndex = originalTopIndex - dy;
panel.Invalidate();
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
p1 = e.Location;
originalLeftIndex = leftIndex;
originalTopIndex = topIndex;
}
因此,对于任何试图这样做的人来说,作为一个概念,以下是如何解决这个问题: 不要只在视口外额外绘制一行/列以节省内存,而是在上、下、左、右边缘的每个方向上绘制整个视口的单元格。。。例如,如果视口可以容纳5个平铺5 X 5=25,则需要在视口外每隔25 X 4=100的方向绘制5 X 5 当 拖动鼠标时,只需移动表单/控件/绘图上已有的控件。。。这样,用户在拖动时不能超出现有分幅的边界。。。例如,如果在拖动最左边的平铺时用鼠标到达右外边缘,则要显示在左边的平铺已经存在!因此,我们只是在跟踪鼠标,如果控件已经存在/没有丢失/问题,这不是问题,因为此时我们没有删除或添加任何平铺 当用户停止在onMouseUp上拖动选定的平铺时,我们将重新计算需要绘制的平铺和不需要绘制的平铺。。。因此,在用户完成拖动后,我们仅在必要时重新绘制添加和/或删除控件,以添加和/或删除整个绘制的平铺集 使用此方法,可以删除任何错误放置的控件、两次生成的控件、缺少的控件以及当鼠标移动太快而无法执行“计算绘制的平铺”代码时出现的任何其他问题。您还可以看到地图在拖动时四处移动,并且始终在屏幕上绘制正确的分幅!问题解决了
然而,我确实发现,当我使用UserControl而不是表单本身时,控件的绘制和更新要比我只将它们添加到表单本身更快更好。。。因此,我接受了将这一方面概括为实际答案的答案,并将其作为一个概念放在这里,以供将来可能想知道如何做到这一点的任何人参考。因此,对于试图这样做的任何人,作为一个概念,这里是如何解决这个问题的: 不要只在视口外额外绘制一行/列以节省内存,而是在上、下、左、右边缘的每个方向上绘制整个视口的单元格。。。例如,如果视口可以容纳5个平铺5 X 5=25,则需要在视口外每隔25 X 4=100的方向绘制5 X 5 拖动鼠标时,只需移动表单/控件/绘图上已有的控件。。。这样,用户在拖动时不能超出现有分幅的边界。。。例如,如果在拖动最左边的平铺时用鼠标到达右外边缘,则要显示在左边的平铺已经存在!因此,我们只是在跟踪鼠标,如果控件已经存在/没有丢失/问题,这不是问题,因为此时我们没有删除或添加任何平铺 当用户停止在onMouseUp上拖动选定的平铺时,我们将重新计算需要绘制的平铺和不需要绘制的平铺。。。因此,在用户完成拖动后,我们仅在必要时重新绘制添加和/或删除控件,以添加和/或删除整个绘制的平铺集 使用此方法,可以删除任何错误放置的控件、两次生成的控件、缺少的控件以及当鼠标移动太快而无法执行“计算绘制的平铺”代码时出现的任何其他问题。您还可以看到地图在拖动时四处移动,并且始终在屏幕上绘制正确的分幅!问题解决了
然而,我确实发现,当我使用UserControl而不是表单本身时,控件的绘制和更新要比我只将它们添加到表单本身更快更好。。。因此,我接受了这个答案,这个答案概括了这方面的实际答案,并将此作为一个概念放在这里,供将来可能想知道如何实现这一点的任何人使用。我将使用DataGridView、TableLayoutPanel、GDI+或其他工具创建网格,然后在拖放中,只需计算新索引并更新索引,不移动网格。我会使用DataGridView、TableLayoutPanel、GDI+或其他工具创建网格,然后在拖放中,只计算新索引并更新索引,而不移动网格。这很好,sorta做了我希望它做的事情。。。但是,虽然这是我试图实现的功能概念的解决方案,但它仍然超出了问题的范围,即:如何使用提供的格式正确实现这一点?例如,您现在的解决方案的一个缺点是,它超出了地图范围/没有在0-1或更大范围内停止,而且,除了数字变化之外,它不会直观地显示地图的移动。。。另一种方式/原始方式,显示移动…超出地图范围/不在0处停止→ 然后通过检查索引值轻松停止。如果您希望移动,它不会直观地显示地图的移动,只需移动TableLayoutPanel并去掉一组GroupBox。我个人将使用基于单个自定义绘制控件的解决方案。所有渲染都将基于一个模型,在自定义控件的OnPain方法中完成。但是这个职位足够长了。我试着用一个简单的代码来解释这个想法,我相信你明白了:在GDI+提供的自定义控件中,你可能可以用更高的性能和更好的视觉支持来做同样的事情。这很好,sorta做了我希望它做的事情。。。但是,虽然这是功能概念的一个解决方案,但我不是
在试图实现这一点时,它仍然超出了问题的范围,即:如何使用提供的格式正确实现这一点?例如,您现在的解决方案的一个缺点是,它超出了地图范围/没有在0-1或更大范围内停止,而且,除了数字变化之外,它不会直观地显示地图的移动。。。另一种方式/原始方式,显示移动…超出地图范围/不在0处停止→ 然后通过检查索引值轻松停止。如果您希望移动,它不会直观地显示地图的移动,只需移动TableLayoutPanel并去掉一组GroupBox。我个人将使用基于单个自定义绘制控件的解决方案。所有渲染都将基于一个模型,在自定义控件的OnPain方法中完成。但是这个职位足够长了。我试着用一个简单的代码来解释这个想法,我相信你明白了:在GDI+提供的自定义控件中,你可能可以用更高的性能和更好的视觉支持来做同样的事情。
int topIndex = 0, leftIndex = 0;
int originalLeftIndex = 0, originalTopIndex = 0;
int cellSize = 100;
Point p1;
TableLayoutPanel panel;
void LayoutGrid()
{
panel.SuspendLayout();
var columns = (ClientSize.Width / cellSize) + 1;
var rows = (ClientSize.Height / cellSize) + 1;
panel.RowCount = rows;
panel.ColumnCount = columns;
panel.ColumnStyles.Clear();
panel.RowStyles.Clear();
for (int i = 0; i < columns; i++)
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, cellSize));
for (int i = 0; i < rows; i++)
panel.RowStyles.Add(new RowStyle(SizeType.Absolute, cellSize));
panel.Width = columns * cellSize;
panel.Height = rows * cellSize;
panel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
panel.ResumeLayout();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
panel = new MyGrid();
this.Controls.Add(panel);
LayoutGrid();
panel.MouseDown += Panel_MouseDown;
panel.MouseMove += Panel_MouseMove;
panel.CellPaint += Panel_CellPaint;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (panel != null)
LayoutGrid();
}
private void Panel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
{
var g = e.Graphics;
TextRenderer.DrawText(g, $"({e.Column + leftIndex}, {e.Row + topIndex})",
panel.Font, e.CellBounds, panel.ForeColor);
}
private void Panel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var dx = (e.Location.X - p1.X) / cellSize;
var dy = (e.Location.Y - p1.Y) / cellSize;
leftIndex = originalLeftIndex - dx;
topIndex = originalTopIndex - dy;
panel.Invalidate();
}
}
private void Panel_MouseDown(object sender, MouseEventArgs e)
{
p1 = e.Location;
originalLeftIndex = leftIndex;
originalTopIndex = topIndex;
}
public class MyGrid : TableLayoutPanel
{
public MyGrid()
{
DoubleBuffered = true;
}
}