由于我在调试Scrolly的下一步时遇到了一些困难-滚动鼠标(非常简单),因此我决定制作一个类似于控制台的类,以便向其添加消息。

这可以让您显示一些消息。它还允许传递您想要的任何对象,但是它将被转换为字符串。您还可以添加信息,错误和警告消息,每种消息,消息和消息的颜色都将匹配。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ConsoleWindow
{
    public sealed partial class Console
    {
        private static volatile Console instance = new Console();
        private static ConsoleForm form = new ConsoleForm();

        public static Console Instance
        {
            get
            {
                return instance;
            }
        }

        public static bool autoShow = true;

        private static string newline = "\r\n";

        private static string line = "================================================================================";

        /// <summary>
        /// Forces the prompt to go to the end of the text.
        /// </summary>
        private static void scrollToEnd()
        {
            form.consoleOutput.SelectionStart = form.consoleOutput.Text.Length;
            form.consoleOutput.ScrollToCaret();
        }

        /// <summary>
        /// Adds a message to the console.
        /// </summary>
        /// <param name="message">Message object to log.</param>
        public static bool Log<T>(T message)
        {
            if (autoShow)
            {
                Show();
            }

            scrollToEnd();
            form.consoleOutput.AppendText(message.ToString() + newline);
            scrollToEnd();

            return true;
        }

        /// <summary>
        /// Adds an error message to the console.
        /// </summary>
        /// <param name="message">Message object to log.</param>
        public static bool Error<T>(T message)
        {
            if (autoShow)
            {
                Show();
            }

            scrollToEnd();
            form.consoleOutput.SelectionColor = Color.Red;
            form.consoleOutput.SelectionLength = 0;

            form.consoleOutput.AppendText("(!) " + message.ToString() + newline);

            scrollToEnd();

            form.consoleOutput.SelectionColor = form.consoleOutput.ForeColor;

            return true;
        }

        /// <summary>
        /// Adds a warning message to the console.
        /// </summary>
        /// <param name="message">Message object to log.</param>
        public static bool Warning<T>(T message)
        {
            if (autoShow)
            {
                Show();
            }

            scrollToEnd();
            form.consoleOutput.SelectionColor = Color.Yellow;
            form.consoleOutput.SelectionLength = 0;

            form.consoleOutput.AppendText(@"/!\ " + message.ToString() + newline);

            scrollToEnd();

            form.consoleOutput.SelectionColor = form.consoleOutput.ForeColor;

            return true;
        }

        /// <summary>
        /// Adds a warning error message to the console.
        /// </summary>
        /// <param name="message">Message object to log.</param>
        public static bool Info<T>(T message)
        {
            if (autoShow)
            {
                Show();
            }

            scrollToEnd();
            form.consoleOutput.SelectionColor = Color.LightBlue;
            form.consoleOutput.SelectionLength = 0;

            form.consoleOutput.AppendText("[!] " + message.ToString() + newline);

            scrollToEnd();

            form.consoleOutput.SelectionColor = form.consoleOutput.ForeColor;

            return true;
        }

        /// <summary>
        /// Clears the console text.
        /// </summary>
        public static void Clear()
        {
            form.consoleOutput.Clear();

            bool autoShowOriginal = autoShow;
            autoShow = false;

            autoShow = autoShowOriginal;
        }

        /// <summary>
        /// Clears the console text. Returns false on exception.
        /// </summary>
        /// <param name="filename">Filename path in a string</param>
        /// <param name="log">Logs a message indicating the operation successfulness</param>
        /// <param name="clear">Clears the console on success</param>
        public static bool SaveToFile(string filename, bool log = true, bool clear = false)
        {
            try
            {
                System.IO.File.WriteAllLines(form.saveFile.FileName, form.consoleOutput.Lines);

                if (clear)
                {
                    Clear();
                }
                if (log == true)
                {
                    Log(line);
                    Info("File saved: " + filename);
                }

                return true;
            }
            catch (System.IO.IOException e)
            {
                if (log == true)
                {
                    Log(line);
                    Error("Failed to save: " + filename);
                    Error(e);
                }

                return false;
            }
        }

        /// <summary>
        /// Closes the console.
        /// </summary>
        public static void Hide()
        {
            form.Hide();
        }

        /// <summary>
        /// Closes the console. Warning: output will be lost!
        /// </summary>
        public static void Close()
        {
            form.Hide();

            Clear();
        }

        /// <summary>
        /// Shows the console.
        /// </summary>
        public static void Show()
        {
            form.Show();
        }


        /// <summary>
        /// Private form functions and elements.
        /// </summary>
        private class ConsoleForm : Form
        {
            private static System.ComponentModel.IContainer components = new System.ComponentModel.Container();

            public RichTextBox consoleOutput = new RichTextBox();
            public ContextMenuStrip consoleMenu = new ContextMenuStrip(components);
            public ToolStripMenuItem consoleMenuSave = new ToolStripMenuItem();
            public ToolStripMenuItem consoleMenuClose = new ToolStripMenuItem();
            public SaveFileDialog saveFile = new SaveFileDialog();

            /// <summary>
            /// Clean up any resources being used.
            /// </summary>
            /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
            protected override void Dispose(bool disposing)
            {
                if (disposing && (components != null))
                {
                    components.Dispose();
                }
                this.Dispose(disposing);
            }

            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InitializeComponent()
            {
                this.consoleMenu.SuspendLayout();
                this.SuspendLayout();
                // 
                // consoleOutput
                // 
                this.consoleOutput.BackColor = System.Drawing.Color.Black;
                this.consoleOutput.BorderStyle = System.Windows.Forms.BorderStyle.None;
                this.consoleOutput.ContextMenuStrip = this.consoleMenu;
                this.consoleOutput.DetectUrls = false;
                this.consoleOutput.Dock = System.Windows.Forms.DockStyle.Fill;
                this.consoleOutput.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
                this.consoleOutput.ForeColor = System.Drawing.Color.Silver;
                this.consoleOutput.Location = new System.Drawing.Point(0, 0);
                this.consoleOutput.Name = "consoleOutput";
                this.consoleOutput.ReadOnly = true;
                this.consoleOutput.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedVertical;
                this.consoleOutput.Size = new System.Drawing.Size(583, 261);
                this.consoleOutput.TabIndex = 0;
                this.consoleOutput.TabStop = false;
                this.consoleOutput.Text = "";
                // 
                // consoleMenu
                // 
                this.consoleMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
                    this.consoleMenuSave,
                    this.consoleMenuClose
                });
                this.consoleMenu.Name = "consoleMenu";
                this.consoleMenu.ShowImageMargin = false;
                this.consoleMenu.Size = new System.Drawing.Size(107, 48);
                // 
                // consoleMenuSave
                // 
                this.consoleMenuSave.Name = "consoleMenuSave";
                this.consoleMenuSave.Size = new System.Drawing.Size(106, 22);
                this.consoleMenuSave.Text = "&Save to file";
                this.consoleMenuSave.Click += new System.EventHandler(this.consoleMenuSave_Click);
                // 
                // consoleMenuClose
                // 
                this.consoleMenuClose.Name = "consoleMenuClose";
                this.consoleMenuClose.Size = new System.Drawing.Size(106, 22);
                this.consoleMenuClose.Text = "&Close";
                this.consoleMenuClose.Click += new System.EventHandler(this.consoleMenuClose_Click);
                // 
                // saveFile
                // 
                this.saveFile.DefaultExt = "txt";
                this.saveFile.Filter = "Text file (*.txt)|*.txt|All files|*.*";
                this.saveFile.Title = "Save console output";
                // 
                // consoleForm
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(583, 261);
                this.Controls.Add(this.consoleOutput);
                this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
                this.MaximizeBox = false;
                this.MinimizeBox = false;
                this.Name = "consoleForm";
                this.ShowIcon = false;
                this.ShowInTaskbar = false;
                this.Text = "Console";
                this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.consoleForm_FormClosing);
                this.consoleMenu.ResumeLayout(false);
                this.ResumeLayout(false);

            }

            public ConsoleForm()
            {
                InitializeComponent();
            }

            private void consoleMenuSave_Click(object sender, EventArgs e)
            {
                if (saveFile.ShowDialog() == DialogResult.OK)
                {
                    Console.SaveToFile(saveFile.FileName);
                }
            }

            private void consoleMenuClose_Click(object sender, EventArgs e)
            {
                Console.Close();
            }

            private void consoleForm_FormClosing(object sender, FormClosingEventArgs e)
            {
                if (e.CloseReason != CloseReason.ApplicationExitCall
                    || e.CloseReason != CloseReason.TaskManagerClosing)
                {
                    e.Cancel = true;

                    Console.Close();
                }
            }
        }
    }
}


我知道这是很长的代码。大部分只是设置元素的属性。大部分是从生成的代码中复制粘贴。我已尝试将其降至最低。

整个想法是将其放入文件中,即可使用。它应该显示在您的第一条消息上。可以很容易地禁用它。

我主要关心的是整个模式,这是...糟糕...真的很糟糕!我有一个带有部分类的名称空间,该部分类带有从Form类继承的私有类...但是,我找不到更好的方法来将表单保留在单独的类中并且仍然私有。

就可读性而言,我还将犯下哪些其他罪行?

#1 楼


public sealed partial class Console


如果没有关联的设计师生成的代码,为什么为什么要使用partial类?提供partial关键字是为了方便,它通过允许一个类的定义跨越多个文件来帮助组织代码。如果只有1个文件,则partial关键字充其量是没有用的,而最糟糕的情况是令人困惑的。 .net控制台应用程序将具有。

private static volatile Console instance = new Console();


为什么Console?为什么Console?即使您要从多个线程访问该类,static引用也会被静态初始化,因为volatile只会增加无用的开销。
查看该类的接口(例如,公共成员)可以给我一个有关为何字段为instance的答案-但是最佳实践告诉我们,如果班级的每个成员都将是volatile,则班级本身也应该为static。这是当前有效的代码:
var console = new ConsoleWindow.Console();

然后,在没有实例成员可以调用的情况下,开发人员将全部使用WTF。通过使类static无效,就可以使构造函数的调用无效,并迫使用户调用静态成员-就像static类一样。 。如果您的班级应为static,则不需要它。而且,如果您真的想要一个Singleton,则应该有一个私有的默认构造函数,以防止其他代码创建您的类的实例...但是使类System.Console是实现同一目标的更好方法。 />

private class ConsoleForm : Form


为什么嵌套?为什么static?为什么它不是static类,而在专用的designer.cs文件中带有设计器代码?

大部分只是设置元素的属性。大部分是从生成的代码中复制粘贴。

很抱歉,但没有。只是...不。
布尔逻辑在这里被破坏了:

if (e.CloseReason != CloseReason.ApplicationExitCall
    || e.CloseReason != CloseReason.TaskManagerClosing)


大声读出来,或者将逻辑求反。
这是错误的:

private void consoleMenuSave_Click(object sender, EventArgs e)
{
    if (saveFile.ShowDialog() == DialogResult.OK)
    {
        Console.SaveToFile(saveFile.FileName);
    }
}


您正在通过其静态成员访问父private类,但是您不需要嵌套类型就可以做到这一点。不论是否嵌套,该窗口都与该父级partial类紧密耦合,并且您在这里拥有的是视图级别的演示者逻辑。
使表格独立存在。

我找不到更好的方法将表单保留在单独的类中并且仍然是私有的。

您不需要私有的表单。您只需要知道更新表单将无济于事,因为表单中没有逻辑...正确完成后。
将您的Console类重命名为Console,删除所有Console -ness(是,请抓取我在该答案的第一部分中上面提到的所有内容!),并使ConsolePresenter在其构造函数中采用一个static实例。
然后从那里获取该嵌套类型,使其与设计器代码所属的设计器代码(在.designer.cs文件中),使其实现ConsolePresenter接口,而不是在表单代码中实现逻辑,并且不直接调用presenter(/ IConsoleView)方法,而引发一个事件,指出“嘿,我是表单-用户刚刚单击了“保存”按钮,您要执行任何操作吗?”
您将需要IConsoleView,因此请创建一个Console类,其中包括它们:
public class ConsoleLinesEventArgs : EventArgs
{
    public ConsoleLinesEventArgs(string[] lines)
    {
        Lines = lines;
    }

    public string[] Lines { get; private set; }
}

现在使用它:
private void consoleMenuSave_Click(object sender, EventArgs e)
{
    OnPromptToSave();
}

public event EventHandler<ConsoleLinesEventArgs> PromptToSave;
protected void OnPromptToSave()
{
    var handler = PromptToSave;
    if (handler != null) 
    {
        var args = new ConsoleLinesEventArgs(consoleOutput.Lines);
        handler(this, args); 
    }
}

Lines事件是EventArgs接口的一部分ace,演示者可以注册:
public Console(IConsoleView view)
{
    view.PromptToSave += view_PromptToSave;
}

private void view_PromptToSave(object sender, ConsoleLinesEventArgs e)
{
    using (var dialog = new SaveFileDialog())
    {
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            SaveToFile(dialog.FileName, e.Lines);
        }
    }
}

请注意PromptToSave实例的生命周期非常短:因为IConsoleView实现了dialog接口,所以最好使任何实例都尽可能短命,并将该实例包装在SaveFileDialog块中,以确保正确处置。 br />看看IDisposable的实现方式...

System.IO.File.WriteAllLines(form.saveFile.FileName, form.consoleOutput.Lines);


在文件顶部添加using,并摆脱这些冗长的命名空间限定符:是SaveToFile想打电话。现在,如果该方法正在从视图上的using System.IO;实例读取文件名,为什么还要传递File.WriteAllLines参数?此方法甚至不需要了解视图,更不用说其fileName成员了,该成员应该是私有的。
通过传递SaveFileDialog的文件名和consoleOutputSaveFileDialog,您将拥有所需的一切将行写入文件中-您不负责获取有问题的行。
一旦视图仅通过事件与演示者对话,并且演示者仅通过其公共接口访问视图,那么您就可以删除该不稳定的Singleton,并使成员成为非静态成员,并让该类实现一些Lines接口,客户端代码可以将其用作可模拟依赖项,而不必与某些静态辅助方法紧密耦合。
代码的最大问题是,您拥有A类(取决于类B)和B类(取决于类A)。重构为Model-View-Presenter模式将使该意大利面条解开。

MVP 101
让我们简化一下(这篇文章已经很长了嗯)-假设只有一个“导出”按钮和一个文本框,并且与该按钮关联的命令需要文本框的内容-文本框是一个实现细节,就像按钮一样。演示者只需知道按钮被单击即可:
public interface IFooView
{
    event EventHandler<ExportEventArgs> ExportContent;
    DialogResult ShowDialog();
}

当用户单击按钮时,视图将引发ConsoleLinesEventArgs事件,以向演示者表示用户要导出文本框的内容。 .NET框架提供了一种携带事件数据的机制-IConsolePresenter类的作用是:
public class ExportEventArgs : EventArgs
{
    public ExportEventArgs(string content)
    {
        Content = content;
    }

    public string Content { get; private set; }
}

然后,您将拥有自己的表格,实现该ExportContent接口: />设计人员必须具有默认构造函数才能实例化表单;通过运行ExportEventArgs方法,它知道如何实例化和初始化表单上的所有控件-该代码是由设计人员生成的,不应被篡改。您甚至不需要查看IFooView文件,如果那里的代码需要更改,最好使用设计器,让设计器重新生成文件。
接下来,事件声明:
public partial class FooView : Form, IFooView
{
    public FooView()
    {
        InitializeComponents();
    }

    public event EventHandler<ExportEventArgs> ExportContent;
    private void OnExportContent()
    {
        var handler = ExportContent;
        if (handler != null)
        {
            var args = new ExportEventArgs(TextBox1.Text);
            handler(this, args);
        }
    }

    private void ExportButton_Click(object sender, EventArgs e)
    {
        OnExportContent();
    }
}

事件是多播委托-它们可以有多个订阅者。该框架为无参数事件提供InitializeComponents,为参数化事件提供通用FooView.designer.cs:遵循这些委托类型是遵循“最少惊喜原则”的最佳方法,因为.NET开发人员希望事件处理程序具有EventHandler签名-但从理论上讲没有什么可以阻止您使用不同的委托人类型声明事件,除非... 。
代表类型明确定义了什么参数,将在需要修改事件以添加,删除或修改参数的那一刻立即中断所有客户端代码:使用EventHandler<T>派生的参数解决了此问题,因为坚持模式您的客户端代码将始终有效,因为只要方法具有void:(object, EventArgs)签名,它就可以处理任何事件,因为在C#中,委托签名中的void可以完美地由采用EventArgs的处理程序处理。 br />因此,事件可以有订阅者-如果您尝试引发没有订阅者的事件,则会引发EventArgs。这就是为什么您看到以下内容的原因:
public event EventDelegateType EventName;

(object,EventArgs)是事件的线程本地副本;提高该副本而不是直接引发事件,使事件引发线程变得安全:另一个线程可以去取消注册FooEventArgs分配与EventArgs检查之间的最后一个处理程序,并且不会发生异常,因为您正在引发事件的线程本地副本。您不必这样做,但是这样做是最佳实践,并且不这样做肯定会在将来咬住您。
注意NullReferenceException作为handler参数被传递:这也是惯例和最佳做法-实践。如果发生无参数事件,您将传递handler =而不是handler != null-这也是一个约定。与执行应完成的任务有关。
演示者是该逻辑所属的地方:
    var handler = ExportContent;
    if (handler != null)
    {
        var args = new ExportEventArgs(TextBox1.Text);
        handler(this, args);
    }

演示者负责实现逻辑。它也可以依赖于其他服务-例如写入数据库所需的this逻辑;您可以在那里实现sender逻辑,但是如果您有几个需要实现的命令,它将很快变得多余:您可以将其抽象到另一个接口之后,然后将该对象注入到您的构造函数中:
public class FooPresenter
{
    private readonly IFooView _view;

    public FooPresenter(IFooView view)
    {
        _view = view;
        _view.ExportContent += View_ExportContent;
    }

    public void Show()
    {
        _view.ShowDialog();
    }

    private void View_ExportContent(object sender, ExportEventArgs e)
    {
        var data = e.Content;
        // do something with data
    }
}

Voilà!
您会得到什么?

一个不依赖任何内容且不实现任何逻辑的视图-基本上,一个视图就是这样:一个演示文稿-专注于类的对象,它严格负责表示逻辑。
依赖于抽象的表示者,因此您可以模拟EventArgs.Empty依赖性并编写实际上不需要提出表单的自动化测试。
模型可以在视图和演示者之间传递数据。

评论


\ $ \ begingroup \ $
哇!!!这是要吸收的大量信息!哇!你真让我震惊!今天我有很多阅读和执行的知识。十分感谢!这无疑对我有很大帮助!之所以说整个……是因为我在某个地方看到它。我想做一个单身人士和一个私人班级。这就是我这样做的原因。
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月17日在8:21



\ $ \ begingroup \ $
@IsmaelMiguel添加了一些MVP-101教程,希望它可以使事情变得清晰。
\ $ \ endgroup \ $
–马修·金登(Mathieu Guindon)♦
15年8月18日在0:31

\ $ \ begingroup \ $
要阅读和实现的内容很多,但是今天我将使用这些新信息再次尝试。十分感谢!
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月18日在8:44

#2 楼


private static string newline = "\r\n";



这是非常错误的。特别是由于.NET的开源,导致在非Windows操作系统上使用C#的可能性增加,我们不应该依赖于特定于平台的换行符。

正确的方法看起来像这样:

private static string newline = Environment.NewLine;


至此,我们只是提供了一个可疑实用程序的别名。


看看如何我们在日志,错误,警告和信息之间有很多重复。这是不必要的。

我们需要一个私有的内部函数来处理我们正在复制的所有内容,然后LogErrorWarnInfo方法只是公开地公开了调用此方法的各种方法。

private static bool ConsolePrint(String message, Color color)
{
    if (autoShow)
    {
        Show();
    }

    scrollToEnd();
    form.consoleOutput.SelectionColor = color;
    form.consoleOutput.SelectionLength = 0;

    form.consoleOutput.AppendText(message + Environment.NewLine);

    scrollToEnd();
    form.consoleOutput.SelectionColor = form.consoleOutput.ForeColor;

    return true;
}


现在我们将其包装为其他方法。

public static bool Log<T>(T message)
{
    return ConsolePrint(message.ToString, form.consoleOutput.ForeColor);
}

public static bool Error<T>(T message)
{
    return ConsolePrint("(!) " + message.ToString, Color.Red);
}

// etc...


评论


\ $ \ begingroup \ $
这实际上是一个不错的选择。我没有意识到!实际上,直到几天前,我对.net的开源还是未知的。这确实是一个不错的提示。谢谢!
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月16日在21:49

\ $ \ begingroup \ $
您忘记了ConsolePrint上的问题。
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月16日在22:14

\ $ \ begingroup \ $
与.NET可能有所不同,但是\ n通常在编程语言中被定义为-the-换行符。例如,在C语言中,\ r \ n会产生与普通编译器上纯\ n相同的结果-单独的\ r仍会按预期工作(将光标移至行的开头)。
\ $ \ endgroup \ $
–更清晰
15年8月16日在23:19

\ $ \ begingroup \ $
我们不应该猜测。我们应该使用语言来提供给我们的属性,这些语言可以检查我们所运行的环境并使用正确的字符。
\ $ \ endgroup \ $
– nhgrif
15年8月16日在23:25

#3 楼

我对WinForms或这种应用程序的设计方式不是很熟悉,因此本文将做一个简短的回顾。

而不是像在这里那样在字符串中键入80个重复的字符:


private static string line = "================================================================================";



如果您拥有.NET 4.0,则可以执行以下操作:

private static string line = String.Concat(Enumerable.Repeat("=", 80));


您也可以执行此操作(感谢@ Mat'sMug):

private static string line = new string('=', 80);



此行:


if (log == true)



可以简单地缩写为:

if (log)


另外,由于您已经是using命名空间System,此行:


catch (System.IO.IOException e)



可以缩写为:

catch (IO.Exception e)


还有其他一些地方,我注意到您使用了前缀System。这些可以删除。

评论


\ $ \ begingroup \ $
String.Concat(Enumerable.Repeat(“ =”,80));是否会降低性能?相对于文字字符串?我的直觉说可能有。
\ $ \ endgroup \ $
– nhgrif
15年8月16日在21:39

\ $ \ begingroup \ $
问题是我不确定我将始终拥有.NET 4.0。实际上,我已经使其与.NET 2.0及更高版本一起运行。是的,它可以毫无问题地在所有这些版本中编译和运行。我一直在迅速寻找解决方法,但他们却忽略了它。
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月16日在21:40

\ $ \ begingroup \ $
@nhgrif可能确实如此,因为调用了两种方法,第一种方法只是在内存中存储一​​些字符。但是,它看起来确实更干净,并且允许使用常量代替80。
\ $ \ endgroup \ $
– SirPython
15年8月16日在21:41

\ $ \ begingroup \ $
@nhgrif我敢肯定它可以用于较大的尺寸,但是对于80的尺寸,我怀疑它是否会起到很大的作用。
\ $ \ endgroup \ $
– Ethan Bierlein
15年8月16日在21:49

\ $ \ begingroup \ $
字符串类具有用于重复字符的(char,int)构造函数。
\ $ \ endgroup \ $
–马修·金登(Mathieu Guindon)♦
15年8月16日在22:00

#4 楼


/// <summary>
/// Closes the console. Warning: output will be lost!
/// </summary>
public static void Close()
{
    form.Hide();

    Clear();
}



您说要关闭表格,但实际上只是将其隐藏并清除数据。实现Form的类型具有Close()方法。


在每个文件中放置单个类/枚举/接口。这将使查找各种类变得容易,因为每个文件与其包含的类型具有相同的名称,并且每个文件都有一个职责。 br />
这看起来很混乱,随着项目的扩大,将很难控制。现在该利用MVP模式了。

首先,创建您的视图必须实现的接口。其中可能包括void Close()DialogResult ShowDialog(),还可能包括void Hide()和其他直接控制表单的自定义方法。

接下来,创建您的表单。用于初始化表单的所有代码都在设计器文件中(但您无需手动将其放置在此处,请使用设计器窗口创建输出)。

现在您创建演示者并DI表单使用您的自定义接口类型,例如public MyPresenter(IView view)。从那里调用ShowDialog()并使用结果来决定要做什么。如果有多个可以单击的按钮,则可能需要使用Show()和触发事件。演示者将处理未直接绑定到视图的所有数据。如果要显示输出,则演示者应该有一个公共方法来获取输出,处理输出,并(通常是单个)调用视图来处理它。例如,这可以是演示者的基本轮廓:

private IView _console;    // assigned from the constructor argument
private static bool ConsolePrint(String message, Color color);
public static bool Log<T>(T message);
public static bool Error<T>(T message);


模型是您使用的任何支持方法和其他对象(例如枚举)。 >现在,您还可以编写单元测试以确保一切正常(您可能希望/需要与此同时使用Moq或其他单元测试框架)。

#5 楼


就可读性而言,我还将犯其他什么罪行?


您将要犯下一个罪行。这大约是一个文件中所需的最大内容量。如果您的命名空间增加,请确保每个大类都获取它自己的文件。它可以使事情井井有条,可维护,并为保持源代码管理日志的整洁(您正在使用源代码管理,对吗?)产生奇迹。

仔细看一下您的函数命名:
/// <summary>
/// Closes the console.
/// </summary>
public static void Hide()
{
    form.Hide();
}


您的摘要指出即将结束。函数名称表示它正在隐藏。在我的英语词典中,这些不一样。除非这是我不熟悉的C#行话,否则这非常令人困惑。隐藏暗示它被推送到后台,而关闭暗示它被终止。

我也忍不住注意到您经常使用以下内容:

if (autoShow)
{
    Show();
}


不要重复自己。您实际上是在整个项目中复制此构造。您可能希望将此类功能推入自己的include中,这些功能可能会也可能不属于您的命名空间。

评论


\ $ \ begingroup \ $
你是对的,我应该对所有输出都采用一种方法。关于关闭和隐藏,这是一个错误的措辞。我只是不知道该写些什么,因为该表格将被“关闭”。但是所有数据都将保留。但是,再次提示!
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年8月16日在21:54