我在Winforms应用程序中执行某些任务时遇到了一个小问题。
我基本上是想在Winform上重新创建“ Top-View RTS Map”。为了节省内存,并非“地图”的所有图块都显示在屏幕上。仅适合视口的那些。因此,我试图允许用户在显示的图块上进行平移/滚动,以浏览整个地图!
现在,我正在通过GroupBox
在运行时动态创建和显示控件来做到这一点。这些代表瓷砖...
我创建了自己的对象来支持所有这些功能(包含屏幕坐标,行和列信息等)。
这是我目前用伪代码完成所有这些操作的方式:
总体上创建表格,图块和地图
我创建一个600px X 600px的Winforms表单。
我创建了一个新的“地图”(使用List<MapTile>
),它是在表单加载时将100个瓦片乘以100个瓦片(用于测试)并将其保存到变量中。
我跟踪显示砖通过另一个列表(或属性从主列表导出bool MapTile.isDrawn
)
每个磁贴在视觉上都是由GroupBox
100px X 100px的控件组成的(因此[7 X 7]可以容纳在屏幕上)
首先,我MapTile
在“地图”中找到中心(瓷砖[50,50]),为其创建并将GroupBox
其放置在表单的中间,
然后,我添加其他必要的图块/控件来填写表格(中心-3个图块,中心+ 3个图块(上,下,左和右))。
当然,每个图块都订阅适当的鼠标事件以执行拖动
当用户鼠标拖动图块时,所有其他显示的图块都会通过更新所有“显示的图块”坐标以匹配“拖动的”图块进行的移动来跟随/跟随领导者。
管理显示的图块
GroupBox
拖动/移动图块时,我进行检查以查看视口外部边缘上的图块是否在其边界之内。到目前为止,只要我不要太快就可以正常工作...但是,当我“太快”拖动磁贴时,它通过了外部边缘(例如:适用于第2点ci-dessus的位置),似乎该应用程序无法跟上,因为它没有在窗体上添加列或行的位置,有时,它没有时间删除行或列的所有控件,而我最终得到了仍然不应该出现在屏幕上。到那时,整个网格/地图将失去平衡并按预期停止工作,因为要么在某个边缘触发的事件不存在(图块不存在),和/或现在有多个控件具有相同的名称,表格,删除或引用失败...
While I am well aware that winforms is not designed to perform intensive GPU/GDI operations, you would think that something this simple would still be easily do-able in winforms?
How would I go about making this more responsive at runtime? Here's my entire set of code:
Form code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RTSAttempt
{
public enum DrawChange
{
None,
Rem_First_Draw_Last,
Rem_Last_Draw_First
};
public partial class Form1 : Form
{
public string selected { get; set; }
private int _xPos { get; set; }
private int _yPos { get; set; }
private bool _dragging { get; set; }
public List<MapTile> mapTiles { get; set; }
public List<MapTile> drawnTiles { get { return this.mapTiles.Where(a => a.Drawn == true).ToList(); } }
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//init globals
this.selected = "";
this._dragging = false;
this.mapTiles = new List<MapTile>();
//for testing, let's do 100 x 100 map
for (int i = 0; i < 100; i++)
{
for (int x = 0; x < 100; x++)
{
MapTile tile = new MapTile(x, i, false, -1, -1, false);
this.mapTiles.Add(tile);
}
}
GenerateStartupTiles();
}
/// <summary>
/// Used to generate the first set of map tiles on screen and dispaly them.
/// </summary>
private void GenerateStartupTiles()
{
//find center tile based on list size
double center = Math.Sqrt(this.mapTiles.Count);
//if not an even number of map tiles, we take the next one after the root.
if (this.mapTiles.Count % 2 != 0)
center += 1;
//now that we have the root, we divide by 2 to get the true center tile.
center = center / 2;
//get range of tiles to display...
int startat = (int)center - 3;
int endat = (int)center + 3;
//because the screen is roughly 600 by 600, we can display 7 X 7 tiles...
for (int row = 0; row < 7; row++)
{
for (int col = 0; col < 7; col++)
{
//get the current tile we are trying to display.
MapTile tile = mapTiles.First(a => a.Row == (startat + row) && a.Col == (startat + col));
//create and define the GroupBox control we use to display the tile on screen.
GroupBox pct = new GroupBox();
pct.Width = 100;
pct.Height = 100;
//find start position on screen
if (row == 0)
pct.Top = -50;
else
pct.Top = -50 + (row * 100);
if (col == 0)
pct.Left = -50;
else
pct.Left = -50 + (col * 100);
tile.X = pct.Left;
tile.Y = pct.Top;
pct.Name = tile.ID;
pct.Tag = Color.LightGray;
//subscribe to necessary events.
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = tile.DisplayID;
//add the tile to the screen
this.Controls.Add(pct);
//set the tile to Drawn mode...
tile.Drawn = true;
}
}
}
private void Pct_MouseUp(object sender, MouseEventArgs e)
{
//self explanatory
if (this._dragging)
{
Cursor.Current = Cursors.Default;
this._dragging = false;
}
}
private void Pct_MouseMove(object sender, MouseEventArgs e)
{
var c = sender as GroupBox;
if (!_dragging || null == c) return;
//get original position, and movement step/distance for calcs.
int newTop = e.Y + c.Top - _yPos;
int newLeft = e.X + c.Left - _xPos;
int movedByX = this.drawnTiles.First(a => a.ID.ToString() == c.Name).X;
int movedByY = this.drawnTiles.First(a => a.ID.ToString() == c.Name).Y;
movedByY = newTop - movedByY;
movedByX = newLeft - movedByX;
//perform all tile movements here
MoveAllTiles(movedByX, movedByY);
}
/// <summary>
/// This method performs all tile movements on screen, and updates the listing properly.
/// </summary>
/// <param name="X">int - the amount fo pixels that the dragged tile has moved horizontally</param>
/// <param name="Y">int - the amount fo pixels that the dragged tile has moved vertically</param>
private void MoveAllTiles(int X, int Y)
{
//used to single out the operation, if any, that we need to do after this move (remove row or col, from edges)
DrawChange colAction = DrawChange.None;
DrawChange rowAction = DrawChange.None;
//move all tiles currently being displayed first...
for (int i = 0; i < this.drawnTiles.Count; i++)
{
//first, determine new coordinates of tile.
drawnTiles[i].Y = drawnTiles[i].Y + Y;
drawnTiles[i].X = drawnTiles[i].X + X;
//find the control
GroupBox tmp = this.Controls.Find(drawnTiles[i].ID, true)[0] as GroupBox;
//perform screen move
tmp.Top = drawnTiles[i].Y;
tmp.Left = drawnTiles[i].X;
tmp.Refresh();
}
//dtermine which action to perform, if any...
if (drawnTiles.Last().Y > this.Height)
rowAction = DrawChange.Rem_Last_Draw_First;
else if ((drawnTiles.First().Y + 100) < 0)
rowAction = DrawChange.Rem_First_Draw_Last;
else
rowAction = DrawChange.None;
if ((drawnTiles.First().X + 100) < 0)
colAction = DrawChange.Rem_First_Draw_Last;
else if (drawnTiles.Last().X > this.Width)
colAction = DrawChange.Rem_Last_Draw_First;
else
colAction = DrawChange.None;
//get currently dispalyed tile range.
int startRow = this.drawnTiles.First().Row;
int startCol = this.drawnTiles.First().Col;
int endRow = this.drawnTiles.Last().Row;
int endCol = this.drawnTiles.Last().Col;
//perform the correct action(s), if necessary.
if (rowAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first row of tiles from the screen
this.drawnTiles.Where(a => a.Row == startRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last row of tiles on screen...
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == endRow + 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.Last().Y + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (rowAction == DrawChange.Rem_Last_Draw_First)
{
//remove last row of tiles
this.drawnTiles.Where(a => a.Row == endRow).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first row of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Row == startRow - 1 && a.Col >= startCol && a.Col <= endCol).ToList();
int newTop = this.drawnTiles.First().Y - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newLeft = (i == 0 ? drawnTiles.First().X : drawnTiles.First().X + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
if (colAction == DrawChange.Rem_First_Draw_Last)
{
//remove the first column of tiles
this.drawnTiles.Where(a => a.Col == startCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add the last column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == endCol + 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.Last().X + 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
else if (colAction == DrawChange.Rem_Last_Draw_First)
{
//remove last column of tiles
this.drawnTiles.Where(a => a.Col == endCol).ToList().ForEach(a => { a.Drawn = false; this.Controls.RemoveByKey(a.ID); this.Refresh(); });
//add first column of tiles
List<MapTile> TilesToAdd = this.mapTiles.Where(a => a.Col == startCol - 1 && a.Row >= startRow && a.Row <= endRow).ToList();
int newLeft = this.drawnTiles.First().X - 100;
for (int i = 0; i < TilesToAdd.Count; i++)
{
int newTop = (i == 0 ? drawnTiles.First().Y : drawnTiles.First().Y + (i * 100));
//create and add the new tile, and set it to Drawn = true.
GroupBox pct = new GroupBox();
pct.Name = TilesToAdd[i].ID.ToString();
pct.Width = 100;
pct.Height = 100;
pct.Top = newTop;
TilesToAdd[i].Y = newTop;
pct.Left = newLeft;
TilesToAdd[i].X = newLeft;
pct.Tag = Color.LightGray;
pct.MouseEnter += Pct_MouseEnter;
pct.MouseLeave += Pct_MouseLeave;
pct.Click += Pct_Click;
pct.Paint += Pct_Paint;
pct.MouseDown += Pct_MouseDown;
pct.MouseMove += Pct_MouseMove;
pct.MouseUp += Pct_MouseUp;
ToolTip tt = new ToolTip();
tt.SetToolTip(pct, pct.Name);
pct.Text = TilesToAdd[i].DisplayID;
this.Controls.Add(pct);
TilesToAdd[i].Drawn = true;
}
}
}
private void Pct_MouseDown(object sender, MouseEventArgs e)
{
//self explanatory
if (e.Button != MouseButtons.Left) return;
_dragging = true;
_xPos = e.X;
_yPos = e.Y;
}
private void Pct_Click(object sender, EventArgs e)
{
//changes the border color to reflect the selected tile...
if (!String.IsNullOrWhiteSpace(selected))
{
if (this.Controls.Find(selected, true).Length > 0)
{
GroupBox tmp = this.Controls.Find(selected, true)[0] as GroupBox;
ControlPaint.DrawBorder(tmp.CreateGraphics(), tmp.ClientRectangle, Color.LightGray, ButtonBorderStyle.Solid);
}
}
GroupBox pct = sender as GroupBox;
ControlPaint.DrawBorder(pct.CreateGraphics(), pct.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
this.selected = pct.Name;
}
private void Pct_Paint(object sender, PaintEventArgs e)
{
//draws the border based on the correct tag.
GroupBox pct = sender as GroupBox;
Color clr = (Color)pct.Tag;
ControlPaint.DrawBorder(e.Graphics, pct.ClientRectangle, clr, ButtonBorderStyle.Solid);
}
private void Pct_MouseLeave(object sender, EventArgs e)
{
//draws the border back to gray, only if this is not the selected tile...
GroupBox pct = sender as GroupBox;
if (this.selected != pct.Name)
{
pct.Tag = Color.LightGray;
pct.Refresh();
}
}
private void Pct_MouseEnter(object sender, EventArgs e)
{
//draws a red border around the tile to show which tile the mouse is currently hovering on...
GroupBox pct = sender as GroupBox;
pct.Tag = Color.Red;
pct.Refresh();
}
}
}
MapTile
object
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RTSAttempt
{
public class MapTile
{
/// <summary>
/// Represents the row of the tile on the map
/// </summary>
public int Row { get; set; }
/// <summary>
/// Represents the column of the tile on the map
/// </summary>
public int Col { get; set; }
/// <summary>
/// Represents the ID of this tile ([-1,-1], [0,0], [1,1], etc
/// </summary>
public string ID { get { return "Tile_" + this.Row + "_" + this.Col; } }
public string DisplayID { get { return this.Row + ", " + this.Col; } }
/// <summary>
/// If this tile is currently selected or clicked.
/// </summary>
public bool Selected { get; set; }
/// <summary>
/// Represents the X screen coordinates of the tile
/// </summary>
public int X { get; set; }
/// <summary>
/// Represents the Y screen coordinates of the tile
/// </summary>
public int Y { get; set; }
/// <summary>
/// Represents whether this tile is currently being drawn on the screen.
/// </summary>
public bool Drawn { get; set; }
public MapTile(int idCol = -1, int idRow = -1, bool selected = false, int screenX = -1, int screenY = -1, bool drawn = false)
{
this.Col = idCol;
this.Row = idRow;
this.Selected = selected;
this.X = screenX;
this.Y = screenY;
this.Drawn = drawn;
}
public override bool Equals(object obj)
{
MapTile tmp = obj as MapTile;
if (tmp == null)
return false;
return this.ID == tmp.ID;
}
public override int GetHashCode()
{
return this.ID.GetHashCode();
}
}
}
I'd create the grid using (DataGridView
, TableLayoutPanel
, GDI+
, or whatever) and then in the drag and drop, just calculate the new indexes and update the indexes, without moving the grid.
Example
The following example shows how to do it using a 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;
}
为防止闪烁:
public class MyGrid : TableLayoutPanel
{
public MyGrid()
{
DoubleBuffered = true;
}
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句