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

透明菜单同步系统

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.80/5 (8投票s)

2002 年 1 月 20 日

8分钟阅读

viewsIcon

71642

downloadIcon

1954

一篇关于半自动化菜单启用/禁用管理的文章

引言

在 GUI 应用程序中,通常需要阻止用户执行界面提供的某些操作。这通常是通过在运行时临时禁用(停用)控件组来实现的。通过这种方式,我们经常使用 GUI 元素的状态来同步应用程序的各种任务。我们可以毫不失一般地将菜单(菜单项)视为这样的 GUI 元素;讨论很容易适用于任何类型的控件。运行时菜单同步通常是手动编写代码完成的,在每个需要此功能的地方。在 GUI 应用程序中,这通常是通过分散在需要进行某种形式同步的代码例程中的启用/禁用代码来实现的。这意味着,当我们需要添加新菜单或定义新逻辑组时,对此类应用程序进行更改的成本很高。这种情况在开发阶段经常发生,在维护阶段则较少发生。下面介绍了一个自动化系统,该系统试图简化 GUI 应用程序中的此类任务。该系统背后的逻辑对程序员来说是透明的。

分析

我们将利用这样一个事实:关于互斥菜单组的大部分知识可以从关于每个菜单元素(存在或添加到程序中)的简单逻辑语句中推导出来。对于每个新添加的菜单项,我们需要指定它所属的菜单组。例如,假设我们定义第一个菜单项 m1。在此之前没有其他菜单项,因此它本身构成一个组,我们将其表示为:{m1}。然后我们添加另一个菜单项 m2。它可以与第一个菜单项在同一组,也可以在新组中。如果 m2m1 在同一组,那么我们仍然只有一个组 {m1,m2}。否则,m2 在一个新组中,我们有两个组 {m1} 和 {m2}。

对于任意两个菜单,只有两种关系可能存在:它们是“朋友”,即它们属于同一组;或者它们是“敌人”,属于两个不同的组。通常,我们不需要为每对菜单指定关系类型,因为这些知识可以推断出来。让我们考虑有三个菜单项 m1m2m3 的情况。如果我们说 m1“是 m2 的朋友”,而 m2“是 m3 的敌人”,那么我们将这三个项目分组为两个“动作”组:{m1,m2} 和 {m3}。因此,我们不必明确说明 m1“是 m3 的敌人”。

我们可以利用这些知识来构建一个管理动作组的系统,其中菜单关系是根据指定的 R 关系推导出来的。在这种系统中,组是自动且透明地创建的,通过检查用户定义的所有“朋友”和“敌人”关系。然后,该系统应提供根据给定菜单项(属于一个或多个隐式组)来启用/禁用隐式组的可能性。此实现的详细信息对该系统的用户来说并不重要。

实际上,这两种关系可以投影为四种操作(关系)

  1. 朋友 '<+>' 操作声明了友谊。如果我们说 m1<+>m2,那么之后我们就有了一个组 {m1,m2}。稍后,我们可以为一个新项目 m3 说 m1<+>m3 或 m2<+>m3,然后我们就有 {m1, m2, m3}。朋友操作也可以合并组:如果我们有组 {m1, m2} 和 {m3,m4},那么任何此类声明 m1<+>m3 或/和 m1<+>m4 或/和 m2<+>m3 或/和 m3<+>m1 或/和 m3<+>m2 或/和 m4<+>m1 或/和 m4<+>m2,都将导致组合并:{m1,m2,m3,m4}。因此,这种关系是自反的(m1<+>m1),对称的(m1<+>m2 == m2<+>m1)和传递的(m1<+>m2 AND m2<+>m3 => m1<+>m3)。它也是结合的,但这在实现中并不需要。根据定义,m1<+>m1 将创建组 {m1},当且仅当 m1 已经不属于任何组。
  2. 敌人 '<->' 操作声明了一个矛盾(负面条件)。如果我们说 m1<->m2,那么之后,我们将创建组 {m1} 和 {m2}。与“朋友”关系相反,“<->”操作不是对称的。因此,如果我们有 {m1,m2,m3} 并声明 m1<->m2,那么我们将得到:{m1} 和 {m2,m3}。这与 m2<->m1 不同,后者将导致形成 {m2} 和 {m1,m3}。因此,此处定义的此操作可能导致组拆分。根据定义,m1<->m1 将创建组 {m1},如果该组尚不存在。
  3. 互为朋友操作 '<*>' 声明了我们称之为定向友谊的东西。它与朋友操作相同,因为它们都声明了“朋友”关系,但与朋友操作不同的是,互为朋友操作不会导致组合并。当我们需要声明一个给定的菜单项在给定时间属于一个以上的操作组时,就需要此操作。因此,如果我们有组 {m1,m2} 和 {m3,m4},并且我们声明 m1<*>m3 或/和 m1<*>m4,我们将得到 {m1,m2} 和 {m1,m3,m4}。因此,两个组都包含 m1 作为互为朋友,因此得名。此操作不对称。操作 m1<*>m1 将创建组 {m1},如果该组尚不存在。
  4. 反敌人操作 '<%>' 也声明了一种类似敌人的关系,但与操作 '<->' 的不同之处在于它不为第一个操作数创建新组。因此,如果我们有 {m1,m2,m3},则 m1<%>m2 得到 {m2,m3},并且不为 m1 创建组。如果我们有 {m1,m2} 和 {m3,m4},则 m1<%>m3 或/和 m1<%>m4 无效。根据定义,m1<%>m1 将删除组 {m1}(如果存在)。这是此操作存在的主要原因:鉴于敌人操作 <-> 会引入新组,那么应该有办法删除它们。根据定义,m1<%>m2 应创建组 {m2}(如果它不存在)。

以上四种操作可用于随时更改组(集群)的状态, successive 的声明可以改变先前声明设置的状态。但请注意,操作定义的顺序并不重要。

该系统应从这些操作中隐式地构建动作组。此过程应该是透明的。但是,在开发过程中,一些用于调试管理器状态(某个时间点形成的组)的方法将很有用,这样它就可以被实现并暴露给用户。

示例

// Legend:
// <+> - declareFriend
// <-> - declareEnemy
// <*> - declareMutualFriend
// <%> - declareAntiEnemy
//
m1<+>m2 # this forms group: {m1,m2}
m2<+>m1 # this is the same: {m1,m2}
m2<->m3 # then: {m1,m2};{m3}
m1<+>m3 # this here causes the group merge of {m1,m2} and {m3} so {m1, m2, m3}
m4<+>m2 # {m1,m2,m3,m4};
m4<->m1 # {m1,m2,m3};{m4}
m1<+>m1 # m1 creates a new group if not already a member of another: {m1,m2,m3};{m4}
m1<->m1 # m1 creates a new group if {m1} does not exists: {m1};{m1,m2,m3};{m4}
m5<->m1 # {m1};{m1,m2,m3};{m4};{m5}
m6<->m1 # {m1};{m1,m2,m3};{m4};{m5};{m6}
m6<+>m5 # {m1};{m1,m2,m3};{m4};{m5,m6}
m1<*>m5 # {m1};{m1,m2,m3};{m4};{m1,m5,m6} - mutual friends: m1 belong to two groups.
m1<%>m2 # {m1};{m2,m3};{m4};{m1,m5,m6}
m1<%>m1 # {m2,m3};{m4};{m1,m5,m6}
m1<+>m3 # {m1,m2,m3,m5,m6};{m4}

实现和演示

实现的主要组件如图所示。它们属于 namespace com_vpcepa::actionGroup

详细说明如下

  • Member<T>(文件:member.h)是围绕要同步的特定 GUI 组件 T 的包装器类。T 类只需要有两个方法
     *  Each member object of class T must have these methods:
     *  string (T::*pf)();  // eg. string Menu::getName();
     *  void (T::*pf)(bool); // eg. Void Menu::setState(bool newState);
     *
     *  Also operator << must be defined for specific member objects T.

    它们将这样使用

    // this is the first thing we must do before
    // we use any of other gmanager objects !!!
    Member<Menu>::setNameMethod(&Menu::getName);
    Member<Menu>::setStateMethod(&Menu::setState);

    一个简单的类 Menu(文件:menu.h, menu.cpp)在演示中使用作为类型 T

  • Group<T>(文件:group.h)- 我们在这里存储对象而不是指针。这不一定总是可取的。实现可以更改为使用指针,即存储 Member<T>* 类型,而不是像现在这样存储 Member<T>。由于成员项不能在一个组中出现多次,因此组只是一个集合。

    Group<T> 类不应直接在代码中访问。

  • GroupManager<T>(文件:groupmanager.h, groupmanager.cpp)实现了所需的逻辑,用于根据其 '<+>'(朋友)和 '<->'(敌人)、'<*>'(互为朋友)和 <%>(反敌人)操作来聚类组件组。可以通过直接在代码中调用其方法 declareFriend()declareEnemy()declareMutualFriend()declareAntiEnemy() 来指定操作,或者使用外部动作文件(这是首选方式)。

    此类各种 '*activate()' 方法用于启用/禁用组。

  • ParseClusters(文件:parseclusters.h, parseclusters.cpp)允许我们根据外部动作文件初始化 GroupManager<T> 对象,从而不直接调用 declareFriend()declareEnemy()declareMutualFriend()declareAntiEnemy()。在此文件中只需要 T 对象的名称及其关系。

    该方法

    void parseAction(GroupManager<T>&, map<string, Member<T> >&, char *) 

    用于从动作文件 'char *' 初始化 'GroupManager<T>'。(name, Member<T>) 组件对应在 map 对象中提供。

    动作文件的格式是

    # The grammar:
    #
    # associationsfile := (association_line)*;
    # association_line := comment | operation | operation comment | empty;
    # comment := '#' + (alfanumeric)*
    # operation := name operator name;
    # name := (alfanumeric)+;
    # operator := '<+>' | '<->' | '<*>' | '<%>'
    # alfanumeric := all keyboard chars
    # empty := an empty line
    #
    # Spaces may separate tokens.

    要在此代码中使用此功能,应包含 'parseclusters.h',并将 'parseclusters.cpp' 代码包含在要编译的文件列表中。

  • MemberCollection<T>(文件:membercollection.h, membercollection.cpp)是一个用于使用 gmanager 系统的实用类。 'gmanager-demo.cpp' 使用此类。有关手动操作的更多详细信息,请参阅 'manager.cpp'。这是使用代码功能的推荐方式。

还使用了其他各种文件

  • gmanager-demo.cpp - gmanager 在应用程序中使用的主要演示。有关其他详细信息,请参阅 'manager.cpp'。
  • vutils.h, vutils.cpp - 此处使用的各种数字和 string 例程。

要在另一个应用程序中使用 gmananger 系统,您不需要 'gmanager-demo.cpp' 文件。

要编译演示,请使用

 CC gmanager-demo.cpp menu.cpp vutils.cpp parseclusters.cpp membercollection.cpp

其中 CC 是任何 C++ 编译器,但代码仅由 Borland BCC32 5.5.1 编译。代码使用了 C++ 异常,可能无法被所有编译器编译。可以通过编辑代码来省略异常。

演示代码不是线程安全的。代码需要一些关键会话包装器代码才能在多线程应用程序中使用,这些应用程序可以从多个线程启用/禁用控件。

许可证

本文没有明确的许可证附加到它,但可能包含在文章文本或下载文件本身中的使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.