GDI函数呼叫分类

博客介绍了组成GDI的几百个函数呼叫的分类,包括取得和释放设备内容的函数、获取设备内容信息的函数、绘图函数、设定和取得设备内容参数的函数以及使用GDI对象的函数,并举例说明了各类型函数的使用场景。

组成GDI的几百个函数呼叫可以分为几大类:

  • 取得(或者建立)和释放(或者清除)设备内容的函数 我们在前面的章节中已经看到过,您在绘图时需要设备内容句柄。GetDC和RealseDC函数让您在非WM_PAINT的消息处理期间来做到这一点,而BeginPaint和EndPaint函数(虽然在技术上它们是USER模块而不是GDI模块的一部分)在进行绘图的WM_PAINT消息处理期间使用。我们马上还会介绍有关设备内容的其它一些函数。
  • 取得有关设备内容信息的函数再以第四章中SYSMETS程序为例,我们使用GetTextMetrics函数来取得有关设备内容中目前所选字体的尺寸信息。在本章后面,我们将看到一个取得非常广泛的设备内容信息的DEVCAPS1程序
     
  • 绘图函数显然,在所有前提条件都得以满足之后,这些函数是真正重要的部分。在上一章中,我们使用TextOut函数在窗口的显示区域显示一些文字。我们将看到,其它GDI函数还可以让您画线、填入区域。在第十四章第十五章还会看到如何建立位图图像。
     
  • 设定和取得设备内容参数的函数设备内容的「属性」决定有关绘图函数如何工作的细节。例如,用SetTextColor来指定TextOut(或者其它文字输出函数)所绘制的文字色彩。在第四章中SYSMETS程序中,我们使用SetTextAlign来告诉GDI:TextOut函数中的字符串的开始位置应该在字符串的右边而不是内定的左边。设备内容的所有属性都有默认值,取得设备内容时这些默认值就设定好了。对于所有的Set函数,都有相应的Get函数,以允许您取得目前设备内容属性。
     
  • 使用GDI对象的函数GDI在这里变得有点混乱。首先举一个例子:内定时使用GDI绘制的所有直线都是实线并具有一个标准的宽度。您可能希望绘制更细的直线,或者是由一系列的点或短划线组成的直线。这种线的宽度和这种线的画笔样式不是设备内容的属性,而是一个「逻辑画笔」的特征。您可以通过在CreatePen、 CreatePenIndirect或ExtCreatePen函数中指定这些特征来建立一个逻辑画笔,这些函数传回一个逻辑画笔的句柄(虽然这些函数被认为是GDI的一部分,但是和大多数GDI函数呼叫不一样,它们不要求设备内容的句柄)。要使用这个画笔,就要将画笔句柄选进设备内容。我们认为,设备内容中目前选中的画笔就是设备内容的一个属性。这样,您画任何线都使用这个画笔,然后,您可以取消设备内容中的画笔选择,并清除画笔对象。清除画笔对象是必要的,因为画笔定义占用了分配的内存空间。除了画笔以外,GDI对象还用于建立填入封闭区域的画刷、字体、位图以及GDI的其它一些方面。
<think>我们有一个需求:保存FrmAcqImageTool窗体的状态(包括控件属性和运行时数据),以便下次打开时自动加载。 我们可以使用序列化技术来实现。但是,窗体的序列化与普通对象不同,因为窗体包含控件树和可能的一些非序列化属性(如图像等)。 我们需要考虑以下步骤: 1. 创建一个可序列化的类(例如:FormState)来保存窗体的状态。这个类应该包含需要保存的控件属性以及运行时数据。 2. 在窗体关闭或保存状态时,将窗体的状态(包括控件的值)收集并序列化到文件中。 3. 在窗体加载时,从文件中反序列化,然后恢复窗体的状态。 但是,直接序列化整个窗体对象可能不可行,因为窗体包含许多非序列化的属性和事件等。因此,我们通常采用保存关键属性值的方式。 参考引用中提到的序列化方法,我们可以使用System.Text.Json(在.NET Core中)或BinaryFormatter(在.NET Framework中,但注意BinaryFormatter在安全性方面存在问题,不推荐使用)或System.Runtime.Serialization.Formatters.Binary。 但是,引用[1]中提到在.NET Framework 4.7.2中不能用Newtonsoft.Json,而采用System.Text.Json。但注意,System.Text.Json是在.NET Core 3.0及以后才引入的,如果我们的项目是.NET Framework 4.7.2,则不能使用System.Text.Json(除非通过NuGet安装兼容包)。不过,我们也可以考虑使用XmlSerializer或DataContractSerializer。 考虑到需求,我们选择一种序列化方式。由于窗体状态可能包含复杂类型,我们选择使用XmlSerializer或BinaryFormatter,但BinaryFormatter有安全风险,且跨平台支持有限。因此,我们可以使用XmlSerializer。 步骤: 1. 定义一个类(例如:FrmAcqImageToolState)来保存需要持久化的状态。这个类应该标记为[Serializable](如果使用BinaryFormatter)或使用默认的Xml序列化(要求有无参数构造函数和可读写属性)。 2. 在窗体类中,实现保存状态的方法:遍历控件,将需要保存的属性值记录到状态对象中。 3. 在窗体类中,实现加载状态的方法:从状态对象中读取值,并赋给窗体的相应控件。 4. 在窗体关闭时(如FormClosing事件)调用保存状态的方法,在窗体加载时(如Load事件)调用加载状态的方法。 但是,由于控件可能很多,我们可以考虑使用递归遍历控件树,但要注意某些控件可能包含子控件(如Panel、GroupBox等)。同时,我们需要避免保存一些不需要持久化的属性(如窗口位置等可能由系统自动保存的,或者图像数据如果很大可以单独保存路径)。 另外,运行时数据可能是一些业务对象,我们需要确保这些对象也是可序列化的。 由于这是一个特定的窗体FrmAcqImageTool,我们可以明确需要保存哪些属性和数据。 下面是一个示例: 步骤1:定义状态类(这里以Xml序列化为例,因为Xml序列化不需要[Serializable]属性,但需要无参构造函数和可读写属性) 步骤2:在窗体中实现保存和加载状态的方法。 步骤3:序列化到文件和从文件反序列化。 示例代码: 首先,定义状态类(注意:为了简化,我们只保存一些基本控件,实际中需要根据具体控件属性保存): 假设窗体上有一个文本框(txtImagePath)和一个复选框(chkEnableFilter),以及一个自定义的运行时数据(比如ImageProcessorSettings对象)。 我们需要让状态类包含这些信息。 例如: ```csharp [Serializable] public class FrmAcqImageToolState { public string ImagePath { get; set; } public bool EnableFilter { get; set; } // 其他需要保存的属性 // 注意:如果包含自定义类型,这些类型也需要可序列化(例如,这里假设ImageProcessorSettings已经标记为[Serializable]) public ImageProcessorSettings ProcessorSettings { get; set; } } ``` 然后,在窗体类中: ```csharp private string stateFilePath = "frmState.xml"; // 保存状态 private void SaveState() { var state = new FrmAcqImageToolState(); state.ImagePath = txtImagePath.Text; state.EnableFilter = chkEnableFilter.Checked; state.ProcessorSettings = this.ImageProcessor; // 假设这个属性是窗体类的运行时数据 // 使用XmlSerializer序列化 var serializer = new XmlSerializer(typeof(FrmAcqImageToolState)); using (var writer = new StreamWriter(stateFilePath)) { serializer.Serialize(writer, state); } } // 加载状态 private void LoadState() { if (!File.Exists(stateFilePath)) return; try { var serializer = new XmlSerializer(typeof(FrmAcqImageToolState)); using (var reader = new StreamReader(stateFilePath)) { var state = (FrmAcqImageToolState)serializer.Deserialize(reader); txtImagePath.Text = state.ImagePath; chkEnableFilter.Checked = state.EnableFilter; this.ImageProcessor = state.ProcessorSettings; // 恢复运行时数据 } } catch (Exception ex) { // 处理异常,例如文件损坏 MessageBox.Show("加载状态失败: " + ex.Message); } } ``` 在窗体事件中挂载: ```csharp private void FrmAcqImageTool_Load(object sender, EventArgs e) { LoadState(); } private void FrmAcqImageTool_FormClosing(object sender, FormClosingEventArgs e) { SaveState(); } ``` 但是,如果窗体上有许多控件,手动为每个控件赋值会很繁琐。我们可以考虑使用反射或循环控件树来收集状态,但这需要处理控件的命名和类型,并且可能遇到复杂控件(如DataGridView)需要特殊处理。 另一种方法是使用属性描述符,但同样复杂。 因此,对于特定窗体,建议明确列出需要保存的控件属性,这样更清晰可控。 另外,如果窗体包含自定义控件,这些控件也需要实现序列化支持(即提供保存和恢复自身状态的方法)。 对于运行时数据,确保相关类都标记为[Serializable](如果使用二进制序列化)或对于Xml序列化,需要有无参构造函数和可读写的公共属性。 注意:如果使用Xml序列化,不能序列化事件,而且只能序列化公共属性和字段。 根据引用[4],我们需要注意在操作完成后关闭文件流(上面代码使用了using,会自动关闭)。 如果使用二进制序列化,代码类似,但需要将XmlSerializer替换为BinaryFormatter,并且文件流使用FileStream(读写模式为二进制)。但注意,BinaryFormatter在.NET 5.0及更高版本中已被标记为过时,所以如果项目未来升级,可能需要考虑其他方式。 考虑到引用[1]提到在.NET Framework 4.7.2中不能使用Newtonsoft.Json,而用户可能希望使用Json,我们可以使用System.Text.Json(通过NuGet安装Microsoft.Extensions.Configuration.Json等包)?但注意,System.Text.Json在.NET Framework 4.7.2中需要安装System.Text.Json包(>=4.7.2版本支持)。不过,如果用户没有特别要求,我们可以选择Xml序列化,因为它是.NET Framework内置的。 因此,我们选择Xml序列化。 但是,如果窗体状态中包含图形对象(如Bitmap),则Xml序列化可能无法直接处理(因为Image没有无参数构造函数,且不是可序列化的)。这时,我们可以将图像保存为文件路径,或者将图像转换为Base64字符串。例如: ```csharp public string ImageBase64 { get { if (Image == null) return null; using (MemoryStream m = new MemoryStream()) { Image.Save(m, ImageFormat.Png); return Convert.ToBase64String(m.ToArray()); } } set { if (value == null) { Image = null; } else { byte[] bytes = Convert.FromBase64String(value); using (MemoryStream ms = new MemoryStream(bytes)) { Image = Image.FromStream(ms); } } } } ``` 然后在状态类中,我们可以用字符串来保存图像。 综上所述,我们按照以下步骤: 1. 定义状态类(FrmAcqImageToolState),包含需要保存的所有属性(字符串、数值、布尔值等基本类型,以及自定义可序列化类型,对于图像等特殊类型转换为字符串或字节数组)。 2. 在窗体中实现保存状态(SaveState)和加载状态(LoadState)方法。 3. 在窗体的Load事件中调用LoadState,在FormClosing事件中调用SaveState。 注意:如果窗体上有多个相同类型的控件(比如多个文本框),我们需要为每个控件单独保存,可以通过控件名称来映射,但更简单的方式是在状态类中为每个需要保存的控件定义一个属性。 例如,状态类: ```csharp public class FrmAcqImageToolState { public string TextBox1Text { get; set; } public bool CheckBox1Checked { get; set; } // ... 其他控件 } ``` 在SaveState中: ```csharp state.TextBox1Text = textBox1.Text; state.CheckBox1Checked = checkBox1.Checked; ``` 在LoadState中: ```csharp textBox1.Text = state.TextBox1Text; checkBox1.Checked = state.CheckBox1Checked; ``` 这样虽然代码量多,但是清晰且安全。 因此,针对FrmAcqImageTool窗体,我们需要明确需要保存哪些控件的哪些属性,然后定义状态类,并实现保存和加载。 最后,注意序列化文件的路径和异常处理。 如果用户希望保存窗口位置和大小,可以保存窗体的Location和Size属性(注意:这些属性是结构体,但Xml序列化支持)。在状态类中加入: ```csharp public Point FormLocation { get; set; } public Size FormSize { get; set; } public FormWindowState WindowState { get; set; } ``` 然后在保存时: ```csharp state.FormLocation = this.Location; state.FormSize = this.Size; state.WindowState = this.WindowState; ``` 加载时: ```csharp this.Location = state.FormLocation; this.Size = state.FormSize; if (state.WindowState != FormWindowState.Minimized) // 避免最小化状态 this.WindowState = state.WindowState; ``` 注意:加载位置时,要确保位置在屏幕可见区域内。 这样,我们就实现了窗体状态的保存和恢复。 如果用户有大量窗体需要保存,可以考虑设计一个通用的机制,但本需求只针对特定窗体。 因此,我们按照上述步骤实现即可。 由于代码需要根据具体窗体控件来编写,以上提供的是框架性代码。 总结步骤: 1. 创建状态类(可序列化)。 2. 在窗体中实现保存和加载状态的方法。 3. 在窗体事件中调用这两个方法。 注意:序列化方式使用XmlSerializer,需要引入System.Xml.Serialization命名空间。 如果用户窗体中有第三方控件,需要确保这些控件的关键属性是可序列化的,或者转换为可序列化类型。 现在,我们根据上述思路编写代码示例(假设FrmAcqImageTool窗体有几个特定控件):</think>### C# Windows Forms 窗体状态保存与恢复方案 要实现 FrmAcqImageTool 窗体及其控件属性和运行时数据的反序列化保存,需要以下步骤: #### 1. 创建可序列化的窗体状态类 ```csharp [Serializable] public class FormState { public Size FormSize { get; set; } public Point FormLocation { get; set; } public FormWindowState WindowState { get; set; } public Dictionary<string, object> ControlStates { get; set; } = new Dictionary<string, object>(); public object RuntimeData { get; set; } // 存储自定义运行时数据 } ``` #### 2. 实现窗体状态的保存方法 ```csharp public FormState SaveFormState() { var state = new FormState { FormSize = this.Size, FormLocation = this.Location, WindowState = this.WindowState }; // 保存控件状态 foreach (Control ctrl in this.Controls) { if (ctrl is TextBox txt) state.ControlStates[ctrl.Name] = txt.Text; if (ctrl is CheckBox chk) state.ControlStates[ctrl.Name] = chk.Checked; if (ctrl is ComboBox cmb) state.ControlStates[ctrl.Name] = cmb.SelectedIndex; // 添加其他控件类型的处理... } // 保存运行时数据 state.RuntimeData = this.AcqImageData; // 替换为实际运行时数据对象 return state; } ``` #### 3. 实现窗体状态的加载方法 ```csharp public void LoadFormState(FormState state) { if (state == null) return; this.Size = state.FormSize; this.Location = state.FormLocation; this.WindowState = state.WindowState; // 恢复控件状态 foreach (Control ctrl in this.Controls) { if (state.ControlStates.TryGetValue(ctrl.Name, out object value)) { if (ctrl is TextBox txt) txt.Text = value as string; if (ctrl is CheckBox chk) chk.Checked = (bool)value; if (ctrl is ComboBox cmb) cmb.SelectedIndex = (int)value; // 添加其他控件类型的处理... } } // 恢复运行时数据 this.AcqImageData = state.RuntimeData as AcqImageData; // 替换为实际类型 } ``` #### 4. 序列化与反序列化工具类 ```csharp public static class FormStateSerializer { public static void Serialize(FormState state, string filePath) { using (FileStream fs = new FileStream(filePath, FileMode.Create)) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(fs, state); } } public static FormState Deserialize(string filePath) { using (FileStream fs = new FileStream(filePath, FileMode.Open)) { BinaryFormatter formatter = new BinaryFormatter(); return (FormState)formatter.Deserialize(fs); } } } ``` #### 5. 在窗体事件中使用 ```csharp private const string StateFilePath = "FrmAcqImageToolState.bin"; private void FrmAcqImageTool_FormClosing(object sender, FormClosingEventArgs e) { FormState state = SaveFormState(); FormStateSerializer.Serialize(state, StateFilePath); } private void FrmAcqImageTool_Load(object sender, EventArgs e) { if (File.Exists(StateFilePath)) { try { FormState state = FormStateSerializer.Deserialize(StateFilePath); LoadFormState(state); } catch (Exception ex) { MessageBox.Show($"加载状态失败: {ex.Message}"); } } } ``` ### 关键注意事项 1. **序列化要求**: - 所有需要保存的自定义数据类型必须标记 `[Serializable]` - 避免保存不可序列化的对象(如 GDI+ 对象) - 对于图像数据,建议保存路径而非完整图像对象[^4] 2. **控件处理**: - 递归处理容器控件(如 Panel、GroupBox) - 特殊控件(如 DataGridView)需要自定义序列化逻辑 - 使用 `Control.Name` 作为字典键确保唯一性 3. **运行时数据**: ```csharp [Serializable] public class AcqImageData { public string LastImagePath { get; set; } public List<ImageProcessingSettings> ProcessingHistory { get; set; } public Rectangle SelectedRegion { get; set; } // 其他运行时状态... } ``` 4. **安全考虑**: - 处理文件不存在或格式错误的情况 - 考虑使用加密存储敏感数据 - 使用 `using` 语句确保资源释放[^4] 此方案通过二进制序列化完整保存窗体状态,包括控件属性值和自定义运行时数据,实现窗体关闭时的自动保存和打开时的自动恢复[^3][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值