1

我正在为用 C 编写的旧开源流体建模引擎开发新的前端。我正在使用 C# 和 WPF。应用程序网络生成需要用户绘制管道、水库、节点、罐等网络。该应用程序或多或少是一个花哨的绘画版本;)

现在我的图形设置如下。我有一个嵌入的 win-forms 面板,它有 mouseclick、mousemove 和paint 事件。鼠标单击将单击的坐标保存到单例类中的数组中,然后通过 触发绘制事件invalidate();。然后,paint 事件循环遍历数组并通过以下方式绘制数组中的所有节点坐标:g.FillEllipse(x,y,20,20); 用户可以单击节点并通过我编写的名为 的函数调出菜单DoesPointExist(xCord, yCord);。它遍历坐标数组,如果 xcord 和 ycord 都在单击坐标的 5px 范围内,则返回 true。一个有点过时的解决方案,但它似乎工作得很好。

到目前为止,这对我来说效果很好。但在未来,我将不得不为每个节点(或面板上的圆圈)赋予越来越多的属性。没什么花哨的,只是必须与每个节点相关联的数值,例如高程。此外,我将需要添加一个选项以在某些时候删除节点。

我可以通过将已删除行的所有值设置为 0 并在paintevents 循环中放入 if 语句以不绘制已删除点来做到这一点,或者甚至弄清楚如何摆脱行期间并将所有其他行向下移动。

我的问题是否有更智能和 OOP 类型的方式来解决这个问题?循环和数组似乎有点过时,必须有更好的方法来利用 C# 功能。我可以设置某种对象或类来简化这个过程吗?该数组很好,但到最后它将是 40-50 列。我的背景更多地基于使用 C 等较低级别语言的函数类型编程。我的程序似乎非常裸露对象和类,而不是用于全局数据的单例玻璃。

我知道给猫剥皮的方法有很多种。但重要的是,我编写的代码对于未来的工程师来说是可访问且易于修改的,因此我想在 OOP 范式中添加尽可能多的内容。我当前的代码非常实用……但不是很整洁。

绘制事件代码:

private void wfSurface_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        Graphics g;
        Graphics h;
        g = wfSurface.CreateGraphics();
        h = wfSurface.CreateGraphics();
        epanet epa = epanet.GetInstance();
        SolidBrush black = new SolidBrush(System.Drawing.Color.Black);
        SolidBrush blue = new SolidBrush(System.Drawing.Color.Pink);
        SolidBrush green = new SolidBrush(System.Drawing.Color.Green);
        System.Drawing.Pen line = new System.Drawing.Pen(System.Drawing.Color.FromArgb(255, 0, 0, 0));

        //Loop to draw vertical grid lines
        for (int f = 50; f < 1100; f += 50)
        {
            e.Graphics.DrawLine(line, f, 0, f, 750);
        }

        //Loop to draw vertical grid lines
        for (int d = 50; d < 750; d += 50)
        {
            e.Graphics.DrawLine(line, 0, d, 1100, d);
        }

        //Loop nodes, tanks, and resevoirs
        for (int L = 1; L < index; L += 1)
        {
            g.FillEllipse(black, Convert.ToInt32(epa.newNodeArray[L, 0] - 8), Convert.ToInt32(epa.newNodeArray[L, 1] - 8), 19, 19);
            h.FillEllipse(blue, Convert.ToInt32(epa.newNodeArray[L, 0] - 6), Convert.ToInt32(epa.newNodeArray[L, 1] - 6), 15, 15);
        }

        for (int b = 1; b < resIndex; b += 1)
        {
            g.FillRectangle(green, Convert.ToInt32(epa.ResArray[b, 0] - 8), Convert.ToInt32(epa.ResArray[b, 1] - 8), 16, 16);
        }
        for (int c = 1; c < tankIndex; c += 1)
        {
            g.FillRectangle(black, Convert.ToInt32(epa.tankArray[c, 0] - 8), Convert.ToInt32(epa.tankArray[c, 1] - 8), 20, 20);
            g.FillRectangle(green, Convert.ToInt32(epa.tankArray[c, 0] - 6), Convert.ToInt32(epa.tankArray[c, 1] - 6), 16, 16);
        }
}

点击事件代码:

private void wfSurface_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        //Initialize epanet and save clicked coordinates to singleton class
        epanet epa = epanet.GetInstance();
        epa.xCord = e.X;
        epa.yCord = e.Y;

        //Check if point exists, if does open property window, doesn't do whatever drawing control is selected
        if (epa.DoesPointExist(e.X, e.Y, index) == false)
        {
            switch (epa.controlSelected)
            {
                case "Node":
                    epa.newSetCords(index, e.X, e.Y);
                    wfSurface.Invalidate();
                    index += 1;

                    break;

                case "Res":
                    epa.setResCords(resIndex, e.X, e.Y);
                    wfSurface.Invalidate();
                    resIndex += 1;
                    wfPanel.Cursor = Cursors.Arrow;
                    break;

                case "Tank":
                    epa.setTankCords(tankIndex, e.X, e.Y);
                    wfSurface.Invalidate();
                    tankIndex += 1;
                    break;

                case "Pointer":
                    break;

                default:
                    //epa.newSetCords(index, e.X, e.Y);
                    wfSurface.Invalidate();
                    break;
            }


        }
        else if (epa.DoesPointExist(e.X, e.Y, index) == true)
        {

            MessageBox.Show("Point Already Exists");
            if (epa.propOpen == false)
            {
                // Open control properties in right pannel
            }

        }

单例类的代码:

public class epanet 

{ 私有静态 epanet 实例 = 新 epanet();

private epanet() { }

public static epanet GetInstance()
{
    return instance;
}

//Microsoft.Win32.SaveFileDialog save = new Microsoft.Win32.SaveFileDialog();

//Network Node Data
public int nodeIndex { get; set; }
public int newNodeIndex { get; set; }
public double xCord { get; set; }
public double yCord { get; set; }
public double x1Cord { get; set; }
public double y1Cord { get; set; }
public int selectedPoint { get; set; }
//public List<double> nodeList = new List<double>();

//Saving Data
public int fileCopyNum { get; set; }
public string filename { get; set; }
public string path { get; set; }
public string fullFileName { get; set; }

//Window Condition Data
public bool drawSurfStatus { get; set; }
public bool windowOpen { get; set; }
public bool OpenClicked { get; set; }
public bool saveASed { get; set; }
public bool newClicked { get; set; }
public bool propOpen { get; set; }

//Drawing Controls
public string controlSelected { get; set; }

//Declare Array to store coordinates
public double[,] nodeArray = new double[100000, 3];
public double[,] newNodeArray = new double[100000, 7];
public double[,] ResArray = new double[100000, 7];
public double[,] tankArray = new double[100000, 7];

public void newSetCords(int newNodeIndex, double xCord, double yCord)
{
    newNodeArray[newNodeIndex, 0] = xCord;
    newNodeArray[newNodeIndex, 1] = yCord;
    newNodeArray[nodeIndex, 2] = nodeIndex;

}

public void setResCords(int newNodeIndex, double xCord, double yCord)
{
    ResArray[newNodeIndex, 0] = xCord;
    ResArray[newNodeIndex, 1] = yCord;
    ResArray[nodeIndex, 2] = nodeIndex;

}

public void setTankCords(int newNodeIndex, double xCord, double yCord)
{
    tankArray[newNodeIndex, 0] = xCord;
    tankArray[newNodeIndex, 1] = yCord;
    tankArray[nodeIndex, 2] = nodeIndex;

}

public void setCords(int nodeIndex, double xCord, double yCord)
{
    nodeArray[nodeIndex, 0] = xCord;
    nodeArray[nodeIndex, 1] = yCord;
    //nodeArray[nodeIndex, 2] = nodeIndex;

}

public bool DoesPointExist(double xcord, double ycord, int index)
{
    int count = 1;
    bool outcome = false;        
    while (count < index)
    {            
        if (Math.Abs(xcord - newNodeArray[count, 0]) < 20)
        {
            if (Math.Abs(ycord - newNodeArray[count, 1]) < 20 )
            {                    
                outcome = true;
                selectedPoint = count;
                index = 0;                    
            }
        }
        count += 1;
    }

    return outcome;        
}

正如我所说,一切正常。我只是在寻找一些关于是否有更专业的方法来做这件事的反馈。

4

1 回答 1

1

如果您使用 C 进行过很多编程,那么您应该熟悉链表的概念。它比数组有用得多,因为它允许您在恒定时间内从列表中的任何点删除。在 C# 中,您需要研究System.Collections.Generic.List如何使用它。

除此之外,标准的面向对象设计是查看您的功能需求并寻找名词,这些应该成为您的类。我在您的问题中看到的最大名词是“节点”。因此,与其拥有一个顶点数组,不如拥有一个节点链表。每个节点都可以具有坐标和高程等属性,如果您需要更多属性,则可以扩展。注意,如果你发现你的 Node 类变得很麻烦,这是一个巨大的迹象,你应该把其他类分开。任何逻辑分组都应该非常连贯地呈现出来。

浏览您包含的 epanet 类。您具有处理文件的功能(文件名,是否另存为等)。这应该变成一个类(在一个单独的文件中),只是为了帮助保持文件的可维护性。您拥有的数组序列,所有相同的主要维度都定义了您的一般节点结构。将这些以及对它们进行操作的函数放入您的 Node 类中。你已经有了有效的逻辑;这对下一位读者来说并不直观。花一点时间质疑所有代码的位置,并问自己是否可以将任何逻辑分组放在一起。

如果您想将自己限制在 Winforms/GDI+,您的一般流程很好,而且您显然可以这样做,但是由于您是在 WPF 中编程,您可能希望在您的项目还很年轻的时候加紧实际使用它. GDI+ 和 WPF 之间的 C# 过渡提供了一些提示,特别是已接受答案的建议 5。网络上还有大量其他参考资料。一个快速的谷歌搜索带来了WPF 用户控件的手动呈现,当然还有MSDN。我会将 WPF 建议留给比我更了解它的其他人。

如果您还不知道要使用 GDI 还是 WPF,那么您肯定需要一个 GDIRenderer 类。理想情况下,您应该有一个 IRenderer 接口和一个实现该接口的 GDIRenderer 类,尽管我确实发现实际上,当您有第二个实现该接口的对象时,创建该接口会更方便。在任何情况下,您的渲染器类都应该将节点列表作为输入。(您目前拥有全局变量,但通常最好只让一部分代码访问它需要的内容,即避免全局变量。)在您当前的代码中,GDIRenderer 将简单地包含并注册您的 OnPaint 函数。但是通过这样做,您将所有渲染隔离到一个类中(应该在它自己的文件中)。然后,如果您决定继续使用 WPF,您可以将 GDIRenderer 替换为 WPFRenderer,它可能包含处理 OnRender 功能所需的所有代码。您想尽一切可能将业务逻辑与渲染方式分开。

于 2013-03-04T15:48:20.587 回答