在VB6中使用按钮标题作为变量有什么不好?

时间:2009-02-27 22:43:08

标签: vb6

我在上一个问题(How to gracefully exit from the middle of a nested subroutine when user cancels?)上收到了一些合理的批评性反馈,用于使用命令按钮的标题作为状态变量。我之所以这样做是因为效率很高,一次只用很少的代码就可以达到两三个目的,但我明白它是如何引起问题的,特别是我最初提出它时略显草率的方式。

我觉得这值得自己讨论,所以这里有相同的想法清理了一下并修改为“正确”(这基本上意味着在一个地方定义字符串,这样你的代码就不会因为你而失败了只需更改命令按钮的文本)。我知道我的变量和控件命名约定很差(OK,不存在),所以请提前道歉。但我想继续关注标题作为状态变量的讨论。

所以我们走了:

' Global variables for this form
Dim DoTheThingCaption(1) As String
Dim UserCancel, FunctionCompleted As Boolean

Private Sub Form_Initialize()
  ' Define the possible captions (is there a #define equivalent for strings?)
  DoTheThingCaption(0) = "Click to Start Doing the Thing"
  DoTheThingCaption(1) = "Click to Stop Doing the Thing"

  ' Set the caption state when form initializes
  DoTheThing.Caption = DoTheThingCaption(0)
End Sub

Private Sub DoTheThing_Click() ' Command Button

If DoTheThing.Caption = DoTheThingCaption(0) Then
  UserCancel = False ' this is the first time we've entered this sub
Else ' We've re-entered this routine (user clicked on button again
     ' while this routine was already running), so we want to abort
  UserCancel = True ' Set this so we'll see it when we exit this re-entry
  DoTheThing.Enabled = False 'Prevent additional clicks
  Exit Sub
End If

' Indicate that we're now Doing the Thing and how to cancel
DoTheThing.Caption = DoTheThingCaption(1)

For i = 0 To ReallyBigNumber
  Call DoSomethingSomewhatTimeConsuming
  If UserCancel = True Then Exit For ' Exit For Loop if requested
  DoEvents ' Allows program to see GUI events
Next

' We've either finished or been canceled, either way
' we want to change caption back
DoTheThing.Caption = DoTheThingCaption(0)

If UserCancel = True Then GoTo Cleanup

'If we get to here we've finished successfully
FunctionCompleted = True
Exit Sub '******* We exit sub here if we didn't get canceled ******* 

Cleanup:
'We can only get to here if user canceled before function completed

FunctionCompleted = False
UserCancel = False ' clear this so we can reenter later
DoTheThing.Enabled = True 'Prevent additional clicks

End Sub  '******* We exit sub here if we did get canceled ******* 

就是这样。还有什么真的 这样做不好吗?这只是一个风格问题吗?是否有其他东西可以以更令人满意或可维护的方式给我这四件事?

  1. 用户按下按钮的即时GUI反馈已导致操作
  2. 如果不希望采取行动,用户眼睛已经在如何取消的位置进行即时GUI反馈
  3. 用户启动/取消操作的一键式方式(减少GUI上的混乱程度)
  4. 一个简单的即时命令按钮禁用以防止多个关闭请求
  5. 我可以看到一个问题可能是代码和GUI之间的紧密耦合(以多种方式),所以我可以看到这对于大型项目(或至少是大型GUI)来说可能会成为一个大问题。这恰好是一个较小的项目,只有2或3个按钮可以接受这种“治疗”。

8 个答案:

答案 0 :(得分:6)

这种技术的最大问题是它使用字符串作为布尔值。根据定义,布尔变量只能有两个状态,而字符串可以有任意数量的状态。

现在,您已经通过依赖一组预定义字符串来定义命令按钮文本的允许值,从而减轻了这种固有的危险。这留下了一些较小的问题:

  • 关于当前和可用状态的逻辑不太明确(实际上有四种可能的状态:未启动,启动,完成,启动但取消) - 维护需要仔细观察潜在的相互作用在按钮文本和布尔变量状态之间,以确定当前状态/应该是什么。单个枚举将使这些状态显式化,使代码更易于阅读和理解,从而简化维护。
  • 您依赖于控件属性(按钮文本)的行为以保持与公开的属性值类型(字符串)的行为一致。这种假设使得旧的VB6代码迁移到更新的语言/平台绝对地狱
  • 字符串比较比布尔变量的简单测试慢得多。在这种情况下,这无关紧要。一般来说,避免它就容易了。
  • 您正在使用DoEvents来模拟多线程(与问题没有直接关系......但是,呃)。

答案 1 :(得分:5)

在处理(非常旧的)代码时,我遇到的最大问题是[全局按钮标题作为变量]全球化是一场噩梦......我不得不移动一个旧的vb6应用程序来使用英语和德语......如果不是几个月,它需要数周时间。

你也在使用goto .....可能需要一些重构来使代码可读?

**编辑以回应评论 我只在每个过程顶部的vb6中使用goto; 出错时转到myErrorHandler。

然后在proc的最底部我会有一个将错误传递给全局处理程序的衬里,以记录错误。

答案 2 :(得分:5)

忽略一般架构/耦合问题,因为你知道这些问题,你的方法的一个问题是因为VB6控件在你设置属性时做了神奇的事情。 您可能认为您只是设置了一个属性,但在许多情况下,您也会引发事件。将复选框值设置为true会触发click事件。在选项卡控件上设置tabindex会导致单击事件。有很多情况。

如果我没记错的话,我也认为有些情况下,如果你设置一个属性,然后立即读取它,你将看不到更新。我相信在看到新值之前必须进行屏幕刷新。

我见过太多可怕的VB6代码,它使用控件属性作为存储。如果您发现这种代码,您将会识别它,因为它分散了对Refresh方法,DoEvents的冗余调用,并且您经常会看到UI被挂起。这通常是由无限循环引起的,其中设置了属性,触发了事件,然后设置了另一个属性,最终有人编写了一行代码,再次更新了第一个属性。

如果这些问题不足以吓到你,那就想想这个。我们中的一些人并不那么聪明。我已经在VB6中编写了超过10年的编码,并亲自编写了大约750K LOC,我一直盯着你上面的例子,我发现很难理解它是怎么回事。假设将来需要阅读您的代码的所有人都会非常愚蠢,并通过编写非常简单的代码让我们感到高兴。

答案 3 :(得分:2)

我认为最好将标题文本与处理状态分离。 goto也让人难以阅读。这是我的重构版本......

Private Const Caption_Start As String = "Click to Start Doing the Thing"
Private Const Caption_Stop As String = "Click to Stop Doing the Thing"

Private Enum eStates
  State_Initialized
  State_Running
  State_Canceled
  State_Completed
End Enum

Private Current_State As eStates

Private Sub Form_Initialize()

  DoTheThing.Caption = Caption_Start
  Current_State = State_Initialized

End Sub

Private Sub DoTheThing_Click()

  If Current_State = State_Running Then

    'currently running - so set state to canceled, reset caption'
    'and disable button until loop can respond to the cancel'
    Current_State = State_Canceled
    DoTheThing.Caption = Caption_Start
    DoTheThing.Enabled = False

  Else

    'not running - so set state and caption'
    Current_State = State_Running
    DoTheThing.Caption = Caption_Stop

    'do the work'
    For i = 0 To ReallyBigNumber
      Call DoSomethingSomewhatTimeConsuming

      'at intervals check the state for cancel'
      If Current_State = State_Canceled Then
        're-enable button and bail out of the loop'
        DoTheThing.Enabled = True
        Exit For
      End If

      DoEvents

    Next

    'did we make it to the end without being canceled?'
    If Current_State <> State_Canceled Then
      Current_State = State_Completed
      DoTheThing.Caption = Caption_Start
    End If

  End If

End Sub

答案 4 :(得分:2)

除了像his answer中的DJ那样删除GOTOS之外,你的方法没有什么不妥。按钮标题只能有两种状态,您可以使用这两种状态在代码中定义流程。

然而,我有两个原因可以区别对待:

  1. 当您希望将程序翻译成其他语言时,您的方法会产生问题(根据我的经验,您应该始终计划),因为字幕会以其他语言更改
  2. 违背了从程序流中分离用户界面的原则。这对你来说可能不是一件很重要的事情,但是当一个程序变得更大,更复杂时,从逻辑中清楚地分离UI会让事情变得更容易。
  3. 总而言之,对于手头的情况,你的解决方案肯定有效,并且没有理由不应该这样做。但另一方面,经验告诉我们,对于更复杂的程序,这种方式可能会导致您可以通过稍微不同的方法轻松避免的问题。

    此外,我认为可以安全地假设每个批评你的例子的人都是这样做的,因为他们在某些时候做出了类似的选择,后来意识到这是一个错误。

    我知道我做到了。

答案 5 :(得分:2)

这将您的基础算法与UI中的特定行为联系起来。现在,如果要更改其中任何一个,则必须对两者进行更改。随着您的应用程序规模的扩大,如果您不通过封装逻辑来保持本地更改,那么维护将成为一场噩梦。

答案 6 :(得分:1)

如果任何人因任何原因需要处理您的代码,他们将找不到他们熟悉和熟悉的实践和惯例,因此功能的界限将不存在。换句话说,你在耦合/凝聚力的方向走错了方向。功能上将状态管理与用户界面相结合是这个问题的典型代表。

你完全了解OOP吗? (不是批评,而是一个合理的问题。如果你这样做,这对你来说会更清楚。即使它只是VB6 OOP。)

答案 7 :(得分:0)

本地化对OP所呈现的逻辑类型影响最大。有几个人提到过 - 如果你需要将应用翻译成中文怎么办?和德国人?俄罗斯人?

你必须添加涵盖这些语言的其他常量......纯粹的地狱。 GUI数据应保持原样,即GUI数据。

这里描述的方法OP让我想起了亨利福德所说的:“只要是黑色的,任何顾客都可以将汽车涂成任何他想要的颜色。”