Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在运行时基于鼠标移动移动窗体上的所有控件_C#_.net_Winforms_Gdi+ - Fatal编程技术网

C# 如何在运行时基于鼠标移动移动窗体上的所有控件

C# 如何在运行时基于鼠标移动移动窗体上的所有控件,c#,.net,winforms,gdi+,C#,.net,Winforms,Gdi+,我在winforms应用程序中执行某项任务时遇到了一些小问题 我基本上是尝试在winform上重新创建俯视RTS地图。为了节省内存,并非地图的所有分幅都显示在屏幕上。仅适用于视口中的对象。因此,我尝试允许用户在显示的平铺上执行平移/滚动,以便浏览整个地图 现在,我通过在运行时动态创建和显示GroupBox控件来实现这一点。这些代表瓷砖 我创建了自己的对象来支持所有这些,包括屏幕坐标、行和列信息等 以下是我目前如何用伪代码完成所有这些: 创建窗体、平铺和地图 我创建了一个winforms表单,它是

我在winforms应用程序中执行某项任务时遇到了一些小问题

我基本上是尝试在winform上重新创建俯视RTS地图。为了节省内存,并非地图的所有分幅都显示在屏幕上。仅适用于视口中的对象。因此,我尝试允许用户在显示的平铺上执行平移/滚动,以便浏览整个地图

现在,我通过在运行时动态创建和显示GroupBox控件来实现这一点。这些代表瓷砖

我创建了自己的对象来支持所有这些,包括屏幕坐标、行和列信息等

以下是我目前如何用伪代码完成所有这些:

创建窗体、平铺和地图

我创建了一个winforms表单,它是600px X 600px

我创建了一个新的映射,使用一个100个tiles x 100个tiles的列表来测试表单加载,并将其保存到一个变量中

我通过从主列表bool MapTile.isDrawn派生的另一个列表或属性跟踪显示的平铺

每个互动程序在视觉上由100px的GroupBox控件组成 X 100px,使其中[7 X 7]个适合屏幕

开始时,我在地图中找到中心地图瓦片[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;
    }
}