65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Visual Studio 中构建仅代码窗体

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.50/5 (5投票s)

2008 年 7 月 25 日

CPOL

12分钟阅读

viewsIcon

23865

downloadIcon

169

如何在 Visual Studio 项目中创建仅代码窗体,并防止 VS 设计器干扰您的代码

引言

我哥哥让我给他发送一个微小的 C# 程序,用于在观看大显示器上的电影时隐藏他的主屏幕。几分钟后,我将以下 BlackScreen.cs 发送给了他。

class BlackScreen : System.Windows.Forms.Form
{   static void Main() { System.Windows.Forms.Application.Run(new BlackScreen()); }
    protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e){Close();}
    protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e) { Close(); }
    BlackScreen()
    {   FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
        StartPosition = System.Windows.Forms.FormStartPosition.Manual;
        TopMost = true; BackColor = System.Drawing.Color.Black;
        Bounds = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
}   }

...并告诉他如何在他的机器上使用 CSC.exe 查找和编译它。他确实要求一个微小的程序……是的,我当时觉得很有趣。

当然,我还是跟进了一个格式正确(并经过修改)的 BlackScreen.cs,嵌入在 Visual Studio (VS) 项目中……考虑到他意图的一部分是熟悉 C# 和 VS。但当他打开项目时,窗体并没有显示在 VS 设计器窗口中。

背景

当我第一次看到 VB 1.0 的设计器窗口时,我惊呆了。所见即所得的开发终于到来了... 终于!

但是,在我在 VB3 时代,在一个非常大的项目中,我需要几个模态窗口。它们不是简单的模态窗口,但它们看起来都非常相似,我确信它们可以被合并成一个窗体。

所以我创建了一个单一窗体,并在 Load 事件中生成了所有必需的模态窗口……调整大小、定位、填充……。当我需要另一个模态窗口时,我只需要稍微修改一下那个 Load 事件中的几行代码,就可以了,另一个模态窗口就生成了。这是我的第一个窗体生成器。

我一直在努力写更少的代码……所以,当我将这种技术应用于应用程序级别的窗体时,我发现了这个

  1. 生成的窗体比手动编辑的窗体看起来好多了。按钮、标签和文本框始终对齐、分隔和调整大小正确,并具有正确的字体和字体大小。没有关于这些问题的 bug 报告。
  2. 通用的生产逻辑使得编写通用的操作逻辑非常容易。例如:所有的点击事件都由一个例程处理,该例程可以根据点击的控件来启用/禁用其他控件,并阻止操作员在前一个操作完成之前进行另一个操作。如果你双击一个控件以进入所见即所得的点击事件,它不会告诉你关于该控件的任何信息。你必须去中心例程。我希望有一个替代方法来将点击事件定向到该函数。
  3. 由于通用的生产和操作代码,如果出现 bug,大多数情况下一切都会崩溃。这是显而易见的,并且可以立即修复。窗体代码很快就变得稳定可靠。
  4. 对于反复出现的设计“这样改”和“那样改”的请求,我很快就学会了只需在窗体代码中添加几行代码,以及一个(最初是隐藏的)开关来切换旧代码或新代码。通常,当设计委员会无法就他们想要的东西达成一致时,我就会说“好吧,让它成为用户的选择。”
    注意:这项技术在未来的原型设计项目中极其有用。
  5. 可执行文件更小,窗体加载速度更快。
  6. 总的来说,项目似乎在加速完成。

我完全倾向于窗体生产而不是窗体编辑。所见即所得编辑器唯一的用处是将控件数组绑定到窗体,因为没有其他方法可以做到。

如果 VB6 和 C++ 结婚生子,那就是 C#。C# 会完全尊重他的父亲 Charlie,但会永远深爱他的母亲(娘家姓 Veronica Baker)的优雅和善良。

当我用 VS 设计器创建我的第一个 C# Windows 应用程序并查看其代码时,我有多高兴。它完全是代码。天哪,我花了那么多时间试图将 VB3 和 VB6 变成 C#……而它就在那里……终于!

BlankScreens.cs

当我把 BlackScreen.cs 发送给朋友时,为了好玩,他开玩笑说,如果背景颜色是白色而不是黑色,他就可以在清洁他的显示器时使用它。显示器?颜色?几分钟后,我有了下面的 BlankScreens.cs

using System; using System.Reflection;
using System.Windows.Forms; using System.Drawing;
[assembly: AssemblyCompany("Appso Associates")]
[assembly: AssemblyCopyright("2006 Appso Associates")]
[assembly: AssemblyProduct("BlankScreens")]
[assembly: AssemblyVersion("1.0.0.*")]
static class BlankScreens
{   //************************************************************************
    // Copyright 2006 Appso Associates (associates@appso.net>
    // CPOL Licensed https://codeproject.org.cn/info/cpol10.aspx
    //************************************************************************
    [STAThread]
    static void Main() { Application.Run(new Display(0)); }
    class Display : Form
    {
        static Display[] displays = new Display[Screen.AllScreens.Length];
        static void disposeAllDisplays()
        {   for (int i = displays.Length - 1; i >= 0; i--)
                displays[i].Dispose();
        }
        static int currentColor = 0;
        static void displayNextColor()
        {   if (++currentColor >= possibleColors.Length) currentColor = 0;
            for (int i = 0; i < displays.Length; i++)
                displays[i].BackColor = possibleColors[currentColor];
        }
        static Color[] possibleColors
        = { Color.Black, Color.White
          , Color.Red, Color.Green, Color.Blue
          , Color.FromArgb(0, 255, 255) // G+B not Red
          , Color.FromArgb(255, 0, 255) // R+B not Green
          , Color.FromArgb(255, 255, 0) // R+G not Blue
          };

        public Display(int index)
        {   Text = "BlankScreens[" + index.ToString() + "]";
            FormBorderStyle = FormBorderStyle.None;
            StartPosition = FormStartPosition.Manual;
            BackColor = possibleColors[currentColor];
            Cursor.Dispose();
            TopMost = true;
            ShowInTaskbar = false;
            Bounds = Screen.AllScreens[index].Bounds;
            if (System.Diagnostics.Debugger.IsAttached)
                Size = new Size(11, 11);
            Show();
            if (index > 0) return; // if it's not the 0 form
            displays[0] = this; // 0 form loads the rest
            for (int i = 1; i < displays.Length; i++)
                displays[i] = new Display(i);
        }
        protected override void OnMouseDown(MouseEventArgs e)
        {   if (e.Button == MouseButtons.Right) displayNextColor();
            else disposeAllDisplays();
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {   if (e.KeyCode == Keys.ControlKey) displayNextColor();
            else disposeAllDisplays();
}   }   }

关注点

  1. BlankScreens.cs 看起来更像一个“.bat”或“.cmd”或“.js”而不是一个“VS 项目”。但就操作系统而言,它是一个 System.Windows.Forms 应用程序,拥有所有相关的优势。它甚至嵌入了清单信息和图标,以便 EXE 程序集能够正确地识别自己。
  2. 它不使用事件。没有创建事件委托数组。无需“管理”事件或创建和 +=-= 事件处理程序。但它拥有对键盘和鼠标的完全访问权限。
  3. 这是一个不依赖于 VS Forms Designer 的仅代码窗体。它是一个纯文本文件,可以用 NotePad.exe 编辑,并由任何可用的 .NET Framework CSC.exe 编译。
  4. 要像用 NotePad.exe 一样轻松地用 VS 编辑代码,窗体类必须是一个“内部类”。VS 不关心“内部类窗体”(稍后会详细介绍)。
  5. VS Forms Designer 在创建这个特定的 Windows 应用程序方面不会有太大帮助。使用 Designer 开发会很困难,并且会涉及更多的代码和需要管理的内容。
  6. 由 VS 编译,它是 16KB 的 EXE。由 CSC 编译,它是 7KB……其中 2KB 是图标。
  7. 它虽然微不足道,但非常有用。即使是现在,黑色的月亮仍然在我的快速启动工具栏上。
    • 当我需要快速隐藏显示器时……我点击黑色的月亮。
    • 当我清洁显示器时,第二次点击(改为白色)会显示要清洁的污渍,或者要令人沮丧的坏点。
    • 当我想要比较显示器时,连续点击会显示显示器如何显示基本颜色。

仅代码窗体

所有 C# VS 设计器设计的窗体实际上都只是代码。因此,我们将“仅代码窗体”定义为代码不依赖于或由所见即所得编辑器生成或生成的窗体。如果您正在创建一个“仅代码窗体”,您将完全负责编写所有窗体元素和控件的实例化、绑定和布局代码。您无法在设计时查看窗体的布局。仅代码窗体仅在运行时可见。

这可能看起来是一项艰巨的任务,没有回报,但如果您努力了,它将带来巨大的回报。

  1. 窗体的许多(如果不是全部)初始属性可以直接从持久化流或嵌入式表中设置……在实例化期间立即设置,无需在 Load 事件中重新初始化即可完全形成。
  2. 一个窗体可以设计用于执行各种不同的任务,使用相同的代码库创建多个不同的窗体……就像 MessageBox 一样。
  3. 这是一种轻松创建仅代码和 abstract 窗体的方法,它封装了通用的应用程序属性和行为。继承该基窗体的应用程序窗体只需要为该应用程序的特定属性和行为进行额外的编码。如果需要更改公共属性(例如:字体),只需在 abstract 类中更改一行代码。事实上,让用户选择他喜欢的字体将是微不足道的编码。
  4. 仅代码窗体是一种用更少的代码和头痛来完成更多功能、更多选项、更多灵活性和更快执行速度的手段。

但是,编程仅代码窗体需要一种不同的思维模式。

MessageBox 是一个仅代码窗体的例子。当您 MessageBox.Show(..) 时,没有静态定义的形状、大小和文本存储在某处。消息框的所有元素(包括图标、按钮、标签)都是直接根据您提供的参数创建的。我很确定这同样适用于 OpenFileDialogFontDialogColorDialog 等。

我热爱 VS。毫无疑问,它是我用过的最好的编程工具。完毕! 我大胆地说。

使用 VS 编码主要是通过敲击键盘,对照“IntelliSense”数据库中在当前输入上下文下正确的名词和动词,然后导航到选择并按 Enter 键的过程。

而且,嗯……右键 -> 重构 -> 重命名……我离不开它。

我只是不希望 VS 干扰我的仅代码窗体。

BlackScreen.cs 作为 VS 项目

尝试使用 BlackScreen 根类进行此实验。打开一个新的“空”项目(像使用 CSC.exe 一样为空)。将 BlackScreen.cs 添加为“现有文件”并进行构建。请注意以下几点:

  1. 您需要将其设为一个 Windows 项目(项目属性 -> 输出类型 -> 从控制台更改为 Windows 应用程序)。
  2. 您需要添加 .NET DLL 引用
    • System.Windows.Forms - 因为您正在访问 Windows Forms
    • System.Drawing - 因为您正在访问“颜色”
    • System - 因为 VS(而不是您)需要访问 System.ComponentModel.Component
  3. 打开解决方案资源管理器并单击“BlackScreen.cs”。出现“BlackScreen [Design]”窗口(而不是代码)……VS 试图帮助您“设计”您的窗口。
  4. 双击该“设计窗口”,并注意到已创建 BlackScreen.resx 文件……这是您并不真正需要或想要的东西。
  5. 查看 BlackScreen.cs 的代码。VS 在“InitializeComponent”中添加了一些新代码……即使您没有使用“组件”。

构建项目。比较 VS 构建的 EXE(16KB)与 CSC 构建的 EXE(4KB)的大小。

运行它们。注意到操作上有什么区别吗?我没有。

内部类窗体

现在打开另一个新的“空”项目。将 BlankScreens.cs 添加为“现有文件”,然后进行构建。

您仍然需要将项目更改为 Windows 项目,并添加上面指定的头两个 .NET DLL 引用。但是,当您在解决方案资源管理器中单击 BlankScreens.cs 时……只会显示代码。没有创建额外的模块,也没有向 BlankScreens.cs 添加额外的代码。BlankScreens.Display 窗体是 BlankScreens static 类的内部类。

请注意,您现在拥有 IntelliSense 文本编辑的所有优势,但 VS 不会对您的仅代码窗体强制执行 VS 设计器。

而且还有一点……通过 IntelliSense 设置窗体属性比使用“属性框”快得多。在任何给定的方法中,输入“this”……就会在菜单中出现一个完整的窗体属性和方法列表。当您向下导航时,它甚至会弹出解释内容的工具提示。请记住,IntelliSense 是一个文本编辑(即仅代码)功能。

使用 BlankScreens.exe

  1. BlankScreens.exe 移动到您想要的位置。
  2. 在您想要的位置创建该 EXE 的快捷方式。
  3. 点击“黑月亮” BlankScreens.exe 或快捷方式。您的所有屏幕都应该变黑。
  4. 按 Ctrl 键或鼠标右键循环浏览颜色。
  5. 按任意其他键或鼠标左键退出。

摘要

所见即所得编辑器有其用武之地。好了,我说完了……所以我们不用再谈论了。请,请不要给我发邮件!

考虑仅代码窗体

  1. 用于对话框和其他“实用窗体”,位于所见即所得项目中。
  2. 用于非常动态的演示应用程序,例如信息亭。
  3. 用于任何小型工具或实用程序应用程序,特别是您自己的个人应用程序。
  4. 每当需要最小的代码大小、最快的速度或灵活且上下文敏感的演示时。

但是,如果您像我一样,掌握了进行这种初始化编码的技巧,您甚至可能偶尔会想……为什么需要所见即所得?

BlankScreens.cs 初学者之旅

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

使用 using 可以简化您需要输入的文本量。例如:using System.Windows.Forms 允许您输入“Form”而不是必须输入“System.Windows.Forms.Form”。您实际上不必使用 using 语句。它们只是让代码更容易阅读和编写。

但是,using System.Reflection 是输入以下内容所必需的

[assembly: AssemblyCompany("Appso Associates")]
[assembly: AssemblyCopyright("2006 Appso Associates")]
[assembly: AssemblyProduct("BlankScreens")]
[assembly: AssemblyVersion("1.0.0.*")]

这是程序集/EXE 清单信息。就像包清单指示包的来源和去向以及内容一样(用于适当的进出口许可证和税收),这段代码标识了程序的内​​容及其来源。

操作系统以多种方式使用此信息,例如:当您将鼠标悬停在文件 "BlankScreens.exe" 上时,资源管理器可能会显示一个工具提示,描述公司和版本。

static class BlankScreens
{   [STAThread]
    static void Main()
    {
        Application.Run(new Display(0));
    }
    class Display : Form
    {
        static Display[] displays = new Display[Screen.AllScreens.Length];

static class BlankScreens”包含入口点 Main(),操作系统在此处为程序分配计算机处理器和内存以便其运行。然后程序执行以下操作:

  1. 创建一个 static Display 数组来管理即将创建的窗体。
  2. 实例化(创建)第一个窗口(窗体)……即 new Display(0)
  3. 将第一个窗口和当前分配的计算机处理器传递给内置的窗口应用程序管理器,后者启动窗口与键盘、鼠标和显示器的交互。

当所有窗体关闭(或处置)后,程序将继续执行 Application.Run 语句之后的行。那里什么都没有,所以,在这种情况下,程序将退出。

BlankScreens.csclass Display 是一个“内部类”,因为它定义在 BlankScreens 类内部。但它也是一个窗体,如“ : Form”所示。因此,它是一个内部类窗体。VS 处理内部类窗体的方式与处理根类窗体不同。相比之下,BlackScreen.cs 是一个根类窗体。

        static void disposeAllDisplays()
        {   for (int i = displays.Length - 1; i >= 0; i--)
                displays[i].Dispose();
        }
        static int currentColor = 0;
        static void displayNextColor()
        {   if (++currentColor >= possibleColors.Length) currentColor = 0;
            for (int i = 0; i < displays.Length; i++)
                displays[i].BackColor = possibleColors[currentColor];
        }
        static Color[] possibleColors
        = { Color.Black, Color.White
          , Color.Red, Color.Green, Color.Blue
          , Color.FromArgb(0, 255, 255) // G+B not Red
          , Color.FromArgb(255, 0, 255) // R+B not Green
          , Color.FromArgb(255, 255, 0) // R+G not Blue
          };

任何一个创建的窗口都可以调用 displayNextColor() 来更改每个窗口的颜色,或者 调用 disposeAllDisplays() 来关闭每个窗口……然后这将退出窗口应用程序管理器,并随后退出 EXE。

        public Display(int index)
        {   Text = "BlankScreens[" + index.ToString() + "]";
            FormBorderStyle = FormBorderStyle.None;
            StartPosition = FormStartPosition.Manual;
            BackColor = possibleColors[currentColor];
            Cursor.Dispose();
            TopMost = true;
            ShowInTaskbar = false;
            Bounds = Screen.AllScreens[index].Bounds;
            if (System.Diagnostics.Debugger.IsAttached)
                Size = new Size(11, 11);
            Show();
            if (index > 0) return; // if it's not the 0 form

            displays[0] = this; // 0 form loads the rest
            for (int i = 1; i < displays.Length; i++)
                displays[i] = new Display(i);
        }

在这里,窗口有机会在“公之于众”之前做好准备……即设置自己以正确的方式显示在正确的屏幕上。当这个实例化方法退出时,窗口将作为 object 获得完全的公民身份,并将与显示器、键盘和鼠标进行交互。

Debugger.IsAttached == true 时,窗口将出现在每个屏幕的左上角,作为小型窗口,让您可以在 VS 中查看和单步执行代码。

第一个窗口(即索引 0),负责实例化所有其他窗口(如果存在)。

注意:实例化(在类意义上)——创建实例。

        protected override void OnMouseDown(MouseEventArgs e)
        {   if (e.Button == MouseButtons.Right) displayNextColor();
            else disposeAllDisplays();
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {   if (e.KeyCode == Keys.ControlKey) displayNextColor();
            else disposeAllDisplays();
}   }   }

这是允许每个窗口检查鼠标点击和键盘按键而无需使用事件的代码。任何被“点击”或“按下”的窗口都将触发右键单击下的 displayNextColor,或 Ctrl 键按下。否则,它将调用 disposeAllDisplays,导致 EXE 退出。

历史

  • 2008-07-25 - 初始文章 - Hello Dapple!(C# 初学者益智游戏)
  • 2009-02-01 - 修改文章 - 在 Visual Studio 中构建仅代码窗体
© . All rights reserved.