1

C で書かれた古いオープン ソースの流体モデリング エンジン用の新しいフロント エンドを開発中です。C# と WPF を使用しています。アプリのネットワーク生成では、ユーザーはパイプ、リザーバー、ノード、タンクなどのネットワークを描く必要があります。アプリは多かれ少なかれ、ペイントの派手なバージョンです ;)

現在、次のようにグラフィックを設定しています。mouseclick、mousemove、およびpaintイベントを持つ組み込みのwin-formsパネルがあります。マウス クリックにより、クリックされた座標がシングルトン クラスの配列に保存され、 を介してペイント イベントがトリガーされますinvalidate();。次に、ペイント イベントが配列をループし、次の方法で配列内のすべてのノード座標を描画しますg.FillEllipse(x,y,20,20); 。ユーザーはノードをクリックして、私が作成した関数DoesPointExist(xCord, yCord);. 座標配列をループし、xcord と ycord の両方がクリックされた座標の 5px 以内にある場合は true を返します。やや古風な解決策ですが、十分に機能しているようです。

これまでのところ、これは私にとってかなりうまくいきました。しかし将来的には、各ノード (またはパネル上の円) にさらに多くのプロパティを与える必要があります。各ノードに関連付ける必要がある高度などの数値だけです。また、ある時点でノードを削除するオプションを追加する必要があります。

削除された行のすべての値を 0 に設定し、if ステートメントを paintevents ループに配置して、削除されたポイントを描画しないようにするか、行の期間を取り除き、他のすべてを下にシフトする方法を見つけ出すことで、おそらくこれを行うことができます。

私の質問は、これを行うためのよりインテリジェントで 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について少し注意を促します。ウェブ上には他にもたくさんの参考文献があります。簡単な Google 検索で、WPF ユーザー コントロールの手動レンダリングと、もちろんMSDNが表示されました。WPF のアドバイスは、私よりもよく知っている他の人に任せます。

GDI と WPF のどちらを使用するかまだわからない場合は、間違いなく GDIRenderer クラスが必要です。理想的には、IRenderer インターフェースとそのインターフェースを実装する GDIRenderer クラスが必要ですが、実際には、それを実装する 2 番目のオブジェクトがある場合にインターフェースを作成する方が便利であることがわかります。いずれにせよ、Renderer クラスはノードのリストを入力として受け取る必要があります。(現在はそれらをグローバルにしていますが、コードの一部だけが必要なものにアクセスできるようにする、つまりグローバルを避けるのが一般的には良いことです。) 現在のコードでは、GDIRenderer は単に OnPaint 関数を含んで登録するだけです。しかし、これを行うことで、すべてのレンダリングを単一のクラス (独自のファイルにある必要があります) に分離します。次に、WPF に移行することにした場合は、OnRender 機能を処理するために必要なすべてのコードを含めることができる WPFRenderer を GDIRenderer に置き換えることができます。ビジネス ロジックをレンダリング方法から分離するために、できる限りのことを行いたいと考えています。

于 2013-03-04T15:48:20.587 に答える