set a font for all child windows

本文介绍了一种使用回调函数配合Win32 API函数EnumChildWindows来为应用程序中所有子窗口快速更改字体的方法。该技术可以轻松应用于主框架窗口、其子视图及状态栏从CMainFrame::OnCreate()中调用,或者用于初始化所有对话框控件的字体从OnInitDialog()中调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

This article shows how to change the font for all child windows using only one line with a callback function.

The idea is to create a callback function that is passed to Win32 API function ::EnumChildWindows(). Somewhere in the application, global CFont object is created. Pointer to this object is passed as a third argument in EnumChildWindows(). This argument is passed to a callback function together with a handle to a child window. Using these 2 arguments, the function changes the font for the child window.

Using this technique, it is very easy to:

  • Change the font for main frame window, its child views and a status bar from CMainFrame::OnCreate().
  • Change the font for all dialog controls from OnInitDialog().

The Callback function looks like this:

 Collapse
// lParam is a pointer to CFont object

BOOL __stdcall SetChildFont(HWND hwnd, LPARAM lparam)
{
   CFont *pFont = (CFont*)lparam;
   CWnd *pWnd = CWnd::FromHandle(hwnd);
   pWnd->SetFont(pFont);
   return TRUE;
}

This callback function is used from OnCreate() or OnInitDialog() in the following way:

 Collapse
...
// g_Font is an object of type CFont or derived from CFont

EnumChildWindows(m_hWnd, ::SetChildFont, &g_Font);
...
<template> <!-- 报警自动转工单预案 --> <div class="alarm-work-order"> <template v-if="companyInfo.companyCode"> <a-spin :spinning="isLoading" :delay="500" :tip="$t('加载中...')"> <div class="edit-view"> <div class="input-item"> <div class="input-lable text-bold"> <span>{{ $t('预案是否启用') }}</span> </div> <div class="input-content"> <a-radio-group v-model="formData.enabled" :default-value="1"> <a-radio :value="1">{{ $t('启用') }}</a-radio> <a-radio :value="0">{{ $t('禁用') }}</a-radio> </a-radio-group> </div> </div> <div class="input-item"> <div class="input-lable text-align"> <span> {{ $t('报警类型:') }}</span> </div> <div class="input-content"> <a-select collapseTags mode="multiple" :maxTagCount="2" v-model="formData.type" allowClear placeholder="请选择" optionFilterProp="label" > <a-select-option v-for="(item, index) in typeOptions" :value="item.value" :key="index"> {{ item.label }} </a-select-option> </a-select> </div> </div> <div class="input-item"> <div class="input-lable"> <span> {{ $t('工单派发配置:') }}</span> </div> <div class="table-box" id="tableBox"> <a-table rowKey="id" :columns="columns" :dataSource="tableData" :pagination="false"> <template slot="staff" slot-scope="text, record, index"> <a-select v-model="record.staffConfig" mode="multiple" placeholder="选择人员" optionFilterProp="label" :maxTagCount="2" allowClear @change="val => handleStaffChange(val, index)" > <a-select-option v-for="person in record.availableStaff" :key="person.id" :label="person.name" :value="person.id" > {{ person.name }} </a-select-option> </a-select> </template> <template slot="time" slot-scope="text, record, index"> <a-select v-model="tableList[index].timeConfig" placeholder="选择期望完成时间"> <a-select-option v-for="option in timeOptions" :key="option.value" :value="option.value"> {{ option.label }} </a-select-option> </a-select> </template> </a-table> </div> </div> <div class="btn-view action-list"> <a-button @click="onSave" type="primary">{{ $t('确定') }}</a-button> <a-button @click="onCancel" type="normal">{{ $t('取消') }}</a-button> </div> </div> </a-spin> </template> </div> </template> <script> export default { props: { orgInfo: { type: Object, default: () => { return {}; }, }, companyInfo: { type: Object, default: () => { return {}; }, }, }, data() { return { isLoading: false, formData: {}, typeOptions: [ { label: '选项1', value: 1, }, { label: '选项2', value: 2, }, { label: '选项3', value: 3, }, { label: '选项4', value: 4, }, ], columns: [ { title: '部门', key: 'department', dataIndex: 'department', ellipsis: true, }, { title: '用户', dataIndex: 'staff', scopedSlots: { customRender: 'staff' }, key: 'staff', }, { title: '期望完成时间设定', dataIndex: 'time', scopedSlots: { customRender: 'time' }, key: 'time', }, ], tableList: [ { id: '012', department: '技术部', staffConfig: ['001', '002'], timeConfig: '24h' }, { id: '013', department: '运维部', staffConfig: ['003'], timeConfig: '48h' }, { id: '014', department: '安全部', staffConfig: [], timeConfig: '7d' }, { id: '015', department: '网络部', staffConfig: [], timeConfig: '24h' }, { id: '016', department: '数据库部', staffConfig: [], timeConfig: '12h' }, ], timeOptions: [ { label: '报警后12小时', value: '12h' }, { label: '报警后24小时', value: '24h' }, { label: '报警后48小时', value: '48h' }, { label: '报警后7日内', value: '7d' }, ], staffList: [ { id: '001', name: '张三' }, { id: '002', name: '李四' }, { id: '003', name: '王五' }, { id: '004', name: '赵六' }, ], }; }, computed: { tableData() { // 收集所有被选中的用户ID const allSelected = new Set(); this.tableList.forEach(row => { if (Array.isArray(row.staffConfig)) { row.staffConfig.forEach(id => allSelected.add(id)); } }); // 为每行生成可用人员列表 return this.tableList.map(row => { const currentSelected = row.staffConfig ? new Set(row.staffConfig) : new Set(); const availableStaff = this.staffList.filter( person => !allSelected.has(person.id) || currentSelected.has(person.id) ); return { ...row, availableStaff, // 添加可用人员列表 }; }); }, }, mounted() {}, methods: { handleStaffChange(value, index) { // 人员只能选择两位 if (value && value.length > 2) { this.$message.destroy(); this.$message.warning('人员最多选择2位'); value = value.slice(0, 2); } this.$set(this.tableList[index], 'staffConfig', value); }, // 确定 onSave() { console.log('保存的数据:', { tableList: this.tableList, }); }, // 取消 onCancel() {}, }, }; </script> <style lang="less" scoped> .alarm-work-order { height: 100%; width: 100%; position: relative; box-sizing: border-box; .edit-view { width: 100%; padding: 46px 32px 24px 32px; .input-item { width: 100%; display: flex; margin-bottom: 22px; &:last-child { margin-bottom: 0 !important; } } .input-lable { width: 88px; min-width: 88px; justify-content: start; display: flex; color: #808e9d; font-size: 14px; line-height: 19px; margin-right: 24px; span { white-space: nowrap; } } .text-bold { font-weight: bold; color: #4e5c6b; } .text-align { align-items: center; } .btn-view { display: flex; align-items: center; justify-content: flex-start; padding-left: 112px; margin-top: 24px; & > button { margin-right: 12px; } } } } </style> 代码评审
最新发布
07-09
<think>我们来进行代码评审,重点检查以下几个方面: 1. 代码逻辑正确性 2. 性能优化 3. 代码可读性和可维护性 4. 潜在的错误和边界情况处理 5. 是否符合Vue最佳实践 ### 1. 代码逻辑正确性 - 在`tableData`计算属性中,我们为每一行计算可用人员列表(`availableStaff`)。这里有一个逻辑:已选的人员(即使在其他行被选中)在当前行仍然可用。这个逻辑符合需求吗?通常,我们可能希望避免同一个人被分配到多个部门,但这里的设计允许同一个人出现在多个部门(只要该人员被当前行选中,即使其他行也选中了,在当前行仍然可用)。这可能是设计上的选择,但需要确认是否符合业务需求。 - 在`handleStaffChange`方法中,我们使用`this.$set`来设置`staffConfig`。这是正确的,因为我们需要确保数组的响应式更新。但是,我们注意到`tableList`中的每个对象已经有了`staffConfig`属性,所以直接赋值也是响应式的(因为对象属性已经是响应式的)。所以这里使用`this.$set`并不是必须的,但也没有问题。 ### 2. 性能优化 - `tableData`计算属性:在每次重新计算时,都会遍历整个`tableList`和`staffList`。如果数据量很大,可能会造成性能问题。可以考虑使用缓存或者优化计算过程。但是,由于数据量不大(部门数不多,人员也不多),所以可以接受。 ### 3. 代码可读性和可维护性 - 代码结构清晰,使用了Vue的单文件组件结构,将模板、脚本和样式分开。 - 计算属性`tableData`的代码可读性较好,但可以添加注释说明其计算逻辑。 - 变量命名基本符合语义化。 ### 4. 潜在的错误和边界情况处理 - 在`handleStaffChange`方法中,当选择的人员超过2位时,我们截取前两位并更新。但是,如果用户连续快速选择多次,可能会出现多次提示。这里我们使用了`this.$message.destroy()`来销毁之前的消息,避免消息堆积,这是好的。 - 在`tableData`计算属性中,我们使用`Set`来收集所有已选人员,然后过滤可用人员。这里有一个边界情况:如果人员ID在`staffList`中不存在,那么过滤时不会包含,这可能导致用户选择了一个不存在的人员ID,然后该人员不会出现在可用人员列表中。但这种情况在代码中不会发生,因为`staffList`是固定的,而且选择框的选项来自`staffList`,所以用户只能选择已有的人员。 ### 5. 是否符合Vue最佳实践 - 使用计算属性来派生数据,符合Vue的响应式原则。 - 在修改数组元素时,使用`this.$set`来确保响应式,虽然在这个特定情况下不是必须的(因为对象属性已经是响应式的),但这是一个好习惯。 - 模板中使用`key`(在`a-select-option`和`a-table`中)来帮助Vue跟踪节点,提高渲染性能。 ### 改进建议 #### 1. 添加注释 在`tableData`计算属性中添加注释,解释可用人员的计算逻辑。 #### 2. 避免不必要的计算 在`tableData`中,我们为每一行都创建了一个新的对象(`{ ...row, availableStaff }`)。这可能会导致不必要的重新渲染,因为即使数据没有变化,每次计算都会生成新对象。我们可以考虑使用缓存,但考虑到数据量不大,可以接受。 #### 3. 表单数据初始化 在`formData`中,我们初始化了一个空对象。但是,在模板中我们使用`v-model="formData.enabled"`,而`enabled`属性一开始不存在,这虽然不会报错(Vue会使其响应式),但最好初始化所有用到的属性。 #### 4. 取消操作 `onCancel`方法目前为空,应该实现取消逻辑,比如关闭对话框或重置表单。 #### 5. 样式优化 - 表格没有边框和背景,可以考虑添加一些样式使其更美观。 - 按钮之间的间距使用`margin-right`,但最后一个按钮也会有空隙,可以使用`:not(:last-child)`选择器或者给父元素设置负边距。 #### 6. 唯一键 在`tableList`中,我们为每一行添加了`id`,并在表格中设置了`rowKey="id"`,这是很好的做法。 ### 修改后的代码 根据以上评审意见,我们进行以下修改: 1. 在`formData`中初始化`enabled`和`type`属性。 2. 在`onCancel`方法中实现取消逻辑(这里假设是关闭当前对话框,但具体需要根据上下文)。 3. 为表格添加一些基本样式,使其更清晰。 4. 在计算属性`tableData`中添加注释。 由于代码较长,以下仅展示修改的部分: ```html <template> <!-- 省略其他部分 --> <div class="table-box" id="tableBox"> <a-table rowKey="id" :columns="columns" :dataSource="tableData" :pagination="false" bordered <!-- 添加边框 --> > <!-- 省略列定义 --> </a-table> </div> <!-- 省略其他部分 --> </template> <script> export default { data() { return { // ...其他数据 formData: { enabled: 1, // 初始化enabled,默认启用 type: [] // 初始化报警类型为空数组 }, // ...其他数据 }; }, methods: { // ...其他方法 onCancel() { // 这里假设需要关闭当前对话框或返回上一级 // 实际逻辑根据上下文实现,例如: this.$emit('cancel'); // 如果该组件被其他组件调用,可以触发cancel事件 } } }; </script> <style lang="less" scoped> /* 添加表格边框样式 */ .table-box { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; } /* 调整按钮间距 */ .btn-view > button:not(:last-child) { margin-right: 12px; } </style> ``` 在计算属性中添加注释: ```javascript computed: { tableData() { // 收集所有被选中的用户ID(跨所有行) const allSelected = new Set(); this.tableList.forEach(row => { if (Array.isArray(row.staffConfig)) { row.staffConfig.forEach(id => allSelected.add(id)); } }); // 为每行生成可用人员列表: // 1. 没有被任何行选中的人员 或者 已经被当前行选中的人员(即使其他行也选中了,在当前行仍然可用) return this.tableList.map(row => { const currentSelected = row.staffConfig ? new Set(row.staffConfig) : new Set(); const availableStaff = this.staffList.filter( person => !allSelected.has(person.id) || currentSelected.has(person.id) ); return { ...row, availableStaff, // 添加可用人员列表 }; }); }, }, ``` ### 总结 代码整体质量不错,结构清晰,逻辑正确。通过上述改进,代码将更加健壮和易维护。 §§相关问题§§: 1. 在Vue中,计算属性和方法有什么区别?何时应该使用计算属性? 2. Vue的响应式系统是如何工作的?为什么有时候需要`this.$set`? 3. 在Vue组件中,如何优化大量数据的渲染性能? 4. 如何确保Vue组件中的样式作用域(scoped)不会影响其他组件?</think># Vue 组件代码评审报告 我将对提供的 Vue 组件进行全面评审,分析其优点、潜在问题和改进建议。 ## 代码优点 1. **组件结构清晰**:模板、脚本和样式分离良好 2. **响应式数据管理**:使用 Vue 的响应式系统管理状态 3. **计算属性优化**:`tableData` 计算属性合理处理了数据依赖 4. **国际化支持**:使用 `$t` 函数支持多语言 5. **用户交互反馈**:提供了选择人数限制的警告提示 6. **唯一键标识**:为表格行添加了唯一 ID ## 需要改进的问题 ### 1. 响应式数据更新问题 ```javascript // 问题代码 v-model="record.staffConfig" ``` **问题**:`record` 是计算属性返回的对象,直接修改它不会影响原始数据源 `tableList` **解决方案**: ```javascript // 修改为 v-model="tableList[index].staffConfig" ``` ### 2. 时间选择器绑定问题 ```javascript // 问题代码 v-model="tableList[index].timeConfig" ``` **问题**:`tableList[index]` 是原始数据,但模板中使用了 `record` 对象 **解决方案**: ```javascript // 修改为 v-model="tableList[index].timeConfig" ``` ### 3. 缺少表单数据初始化 ```javascript // 问题代码 formData: {} ``` **问题**:未初始化表单数据可能导致绑定错误 **解决方案**: ```javascript formData: { enabled: 1, type: [] } ``` ### 4. 缺少组件卸载清理 ```javascript // 问题代码 mounted() {} ``` **问题**:未处理组件卸载时的清理工作 **解决方案**: ```javascript mounted() { // 初始化操作 }, beforeDestroy() { // 清理操作,如取消请求 } ``` ### 5. 样式细节问题 ```less // 问题代码 .btn-view > button { margin-right: 12px; } ``` **问题**:最后一个按钮也有右边距,导致布局不均衡 **解决方案**: ```less .btn-view > button:not(:last-child) { margin-right: 12px; } ``` ## 改进后的完整代码 ```html <template> <div class="alarm-work-order"> <template v-if="companyInfo.companyCode"> <a-spin :spinning="isLoading" :delay="500" :tip="$t('加载中...')"> <div class="edit-view"> <div class="input-item"> <div class="input-lable text-bold"> <span>{{ $t('预案是否启用') }}</span> </div> <div class="input-content"> <a-radio-group v-model="formData.enabled"> <a-radio :value="1">{{ $t('启用') }}</a-radio> <a-radio :value="0">{{ $t('禁用') }}</a-radio> </a-radio-group> </div> </div> <div class="input-item"> <div class="input-lable text-align"> <span>{{ $t('报警类型:') }}</span> </div> <div class="input-content"> <a-select collapseTags mode="multiple" :maxTagCount="2" v-model="formData.type" allowClear placeholder="请选择" optionFilterProp="label" > <a-select-option v-for="(item, index) in typeOptions" :value="item.value" :key="index" > {{ item.label }} </a-select-option> </a-select> </div> </div> <div class="input-item"> <div class="input-lable"> <span>{{ $t('工单派发配置:') }}</span> </div> <div class="table-box" id="tableBox"> <a-table rowKey="id" :columns="columns" :dataSource="tableData" :pagination="false" bordered > <template slot="staff" slot-scope="text, record, index"> <a-select v-model="tableList[index].staffConfig" mode="multiple" placeholder="选择人员" optionFilterProp="label" :maxTagCount="2" allowClear @change="val => handleStaffChange(val, index)" > <a-select-option v-for="person in record.availableStaff" :key="person.id" :label="person.name" :value="person.id" > {{ person.name }} </a-select-option> </a-select> </template> <template slot="time" slot-scope="text, record, index"> <a-select v-model="tableList[index].timeConfig" placeholder="选择期望完成时间" > <a-select-option v-for="option in timeOptions" :key="option.value" :value="option.value" > {{ option.label }} </a-select-option> </a-select> </template> </a-table> </div> </div> <div class="btn-view action-list"> <a-button @click="onSave" type="primary">{{ $t('确定') }}</a-button> <a-button @click="onCancel" type="normal">{{ $t('取消') }}</a-button> </div> </div> </a-spin> </template> </div> </template> <script> export default { props: { orgInfo: { type: Object, default: () => ({}) }, companyInfo: { type: Object, default: () => ({}) } }, data() { return { isLoading: false, formData: { enabled: 1, type: [] }, typeOptions: [ { label: '服务器宕机', value: 1 }, { label: '网络中断', value: 2 }, { label: '数据库异常', value: 3 }, { label: '应用服务异常', value: 4 } ], columns: [ { title: '部门', key: 'department', dataIndex: 'department', ellipsis: true, width: '25%' }, { title: '用户', dataIndex: 'staff', scopedSlots: { customRender: 'staff' }, key: 'staff', width: '45%' }, { title: '期望完成时间设定', dataIndex: 'time', scopedSlots: { customRender: 'time' }, key: 'time', width: '30%' } ], tableList: [ { id: '012', department: '技术部', staffConfig: ['001', '002'], timeConfig: '24h' }, { id: '013', department: '运维部', staffConfig: ['003'], timeConfig: '48h' }, { id: '014', department: '安全部', staffConfig: [], timeConfig: '7d' }, { id: '015', department: '网络部', staffConfig: [], timeConfig: '24h' }, { id: '016', department: '数据库部', staffConfig: [], timeConfig: '12h' } ], timeOptions: [ { label: '报警后12小时', value: '12h' }, { label: '报警后24小时', value: '24h' }, { label: '报警后48小时', value: '48h' }, { label: '报警后7日内', value: '7d' } ], staffList: [ { id: '001', name: '张三' }, { id: '002', name: '李四' }, { id: '003', name: '王五' }, { id: '004', name: '赵六' } ] }; }, computed: { tableData() { const allSelected = new Set(); // 收集所有被选中的用户ID this.tableList.forEach(row => { if (Array.isArray(row.staffConfig)) { row.staffConfig.forEach(id => allSelected.add(id)); } }); // 为每行生成可用人员列表 return this.tableList.map(row => { const currentSelected = row.staffConfig ? new Set(row.staffConfig) : new Set(); const availableStaff = this.staffList.filter( person => !allSelected.has(person.id) || currentSelected.has(person.id) ); return { ...row, availableStaff }; }); } }, methods: { handleStaffChange(value, index) { // 人员只能选择两位 if (value && value.length > 2) { this.$message.destroy(); this.$message.warning('人员最多选择2位'); value = value.slice(0, 2); } // 确保响应式更新 this.$set(this.tableList[index], 'staffConfig', value); }, // 确定 onSave() { this.isLoading = true; // 模拟API保存 setTimeout(() => { console.log('保存的数据:', { formData: this.formData, tableList: this.tableList }); this.$message.success('配置已保存'); this.isLoading = false; }, 500); }, // 取消 onCancel() { this.$confirm({ title: '确认取消', content: '所有未保存的更改将会丢失,确定取消吗?', onOk: () => { this.$message.info('操作已取消'); // 实际应用中可能需要重置表单或关闭弹窗 } }); } } }; </script> <style lang="less" scoped> .alarm-work-order { height: 100%; width: 100%; position: relative; box-sizing: border-box; .edit-view { width: 100%; padding: 46px 32px 24px 32px; .input-item { width: 100%; display: flex; margin-bottom: 22px; &:last-child { margin-bottom: 0 !important; } } .input-lable { width: 88px; min-width: 88px; justify-content: start; display: flex; color: #808e9d; font-size: 14px; line-height: 19px; margin-right: 24px; span { white-space: nowrap; } } .text-bold { font-weight: bold; color: #4e5c6b; } .text-align { align-items: center; } .table-box { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; margin-top: 8px; } .btn-view { display: flex; align-items: center; justify-content: flex-start; padding-left: 112px; margin-top: 24px; > button:not(:last-child) { margin-right: 12px; } } } } </style> ``` ## 关键改进说明 ### 1. 修复数据绑定问题 ```javascript // 人员选择器 v-model="tableList[index].staffConfig" // 时间选择器 v-model="tableList[index].timeConfig" ``` 直接绑定到原始数据源确保数据更新正确 ### 2. 增强表单管理 ```javascript formData: { enabled: 1, type: [] } ``` 正确初始化表单数据,避免未定义错误 ### 3. 改进保存方法 ```javascript onSave() { this.isLoading = true; // 模拟保存操作 setTimeout(() => { this.$message.success('配置已保存'); this.isLoading = false; }, 500); } ``` 添加加载状态和成功反馈,提升用户体验 ### 4. 完善取消操作 ```javascript onCancel() { this.$confirm({ title: '确认取消', content: '所有未保存的更改将会丢失,确定取消吗?', onOk: () => { this.$message.info('操作已取消'); } }); } ``` 添加确认对话框,防止误操作 ### 5. 优化样式细节 ```less // 按钮间距优化 > button:not(:last-child) { margin-right: 12px; } // 表格添加边框 .table-box { border: 1px solid #e8e8e8; border-radius: 4px; } ``` ## 最佳实践建议 1. **组件拆分**:当组件复杂度增加时,考虑拆分为多个小组件 2. **API 服务抽象**:创建独立的 API 服务模块 3. **错误处理**:添加全局错误处理机制 4. **表单验证**:在保存前添加表单验证逻辑 5. **性能优化**:对于大型数据使用虚拟滚动 6. **类型安全**:考虑使用 TypeScript 增强类型安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值