zoukankan      html  css  js  c++  java
  • 关于滚动更新的设计技巧

    前言

    程序设计中,常常有这样的一个处理场景。需要批量处理一个列表的内容,但在列表条目的设计中,有基于条目的列表处理事件,这样的事件其实是重复覆盖的。

    这种状态下,往往是需要屏蔽掉条目事件,进行列表整体处理完毕后,再恢复条目事件的。

    举一个常规的例子,一般单据界面的设计模式,左边一个竖型单据号列表,右边是针对这个列表当前单据的内容明细,如下所示:

    单据号列表                         单据号: B002   单据描述信息: xxxxxxxxx

    B001                                  单据内容: xxxxxxxxxxxxxxx

    B002

    B003

    当这个单据列表发生滚动的时候,例如当前单据由B002改变为B003时,右边的整个内容都会随之改变。可是,在整个列表刷新的过程中,如果不断的触发右边内容

    的刷新,就完全不必要了,不仅仅会占用系统大量资源,还可能让程序卡死。 这样的情况,在涉及二级表和三级表的滚动更新模式时,也是同样的问题。

    那么我们今天就针对这个设计技巧来谈谈如何完美实现。                                                       ------By Murphy

    1,不优良的设计模式

    a,增加一个是否刷新变量标识,例如FBusy : boolean;

    假设,刷新某条单据的方法为RefreshBill( ABillNo : string);   刷新单据列表的方法是RefreshList;

    那么,在刷新单据的过程中可以这么写:

    procedure RefreshBill(ABillNo : string);

    begin

        if FBusy then

        exit;

       ....  //这里是刷新某条单据内容的语句

    end;

    而在刷新单据列表的过程中,可以这么用:

    procedure RefreshList;

    begin 

        FBusy := True;             //先更改刷新标记

        ...  //列表滚动的过程

        FBusy := False;          //标记还原

        RefreshBill(CurrentBill);  //独立进行一次单据刷新

    end;

    这种设计,逻辑上是完整的。但这样的设计有什么问题呢?首先就是对标志位FBusy的判断十分繁琐,不仅仅是RefreshList中需要判断,往往在增删改存处理上,

    都需要不断的判断调用。而当出现多级表的时候,例如一个单据内容有多个二级三级表的时候,就必须创建多个标记,这些标记在各种事件中的判断与更改,经过

    叠加以后,会变得极为复杂,给设计带来不小的难度。

    b,还有一种设计,与做是否刷新单据标记是同样的道理,是通过屏蔽事件来实现的。

    例如刷新单据内容的过程是写在List的滚动事件中的:

    procedure ListAfterScroll(Sender : TObject);

     begin

          ...//这里是刷新某条单据内容的语句

     end;

    而调用刷新List的时候,需要做这样的处理:

    procedure QueryList;

    begin

         List.OnAfterScroll := nil;

         ...//列表滚动的过程

         List.OnAfterScroll := ListAfterScroll; //还原事件设置。

    end;

    这种设置跟设置刷新标记其实是一样的,一旦出现多表联动,就变得异常复杂。

    并且在使用的时候,如果没注意处理好异常,很容易出现界面控件功能乱掉的情况。

    2,高效稳定的处理方式:

    a,我们先看看强大的VCL是如何处理这种情况的,就拿TStrings类处理UpdateState为例,下面是我从VCL中截取的一段源码,

    无关的代码部分,我用省略号代替:

    type
    TStrings = class(TPersistent)
       private
         ...
         FUpdateCount: Integer;  //定义一个计数器标记,由于是一个对象静态变量,所以系统初始化为0,没作额外初始化。
        ...

      protected

         ...

         property UpdateCount: Integer read FUpdateCount;   //这里将计数器以只读属性的标识表达出来,如果大于0则表示正在更新中。

         ...
      public
        ...
        procedure BeginUpdate;   //TStrings列表更新开始
        ...
        procedure EndUpdate;     //TStrings列表更新结束
       ...
       procedure SetUpdateState(Updating: Boolean); virtual;  //更新的实际过程,系统给了一个虚方法,根据派生不同的LIST列表进行实化。
       ...
    procedure TStrings.BeginUpdate;
    begin
    if FUpdateCount = 0 then SetUpdateState(True);     //如果计数器为0,才进行刷新,系统这里明确区分出了刷新开始和刷新结束过程(即这里的参数True)。
    Inc(FUpdateCount);
    end;
    ...
    procedure TStrings.EndUpdate;
    begin
    Dec(FUpdateCount);                                              //处理完计数器减1
    if FUpdateCount = 0 then SetUpdateState(False);  //作结尾的刷新处理,注意参数False。
    end;
    ...
    procedure TStrings.SetUpdateState(Updating: Boolean);  //对单条Item的刷新处理,这里还没有实化。
    begin
    end;

    以上的代码就是处理滚动更新的核心代码了,有这样的设置,就可以很好的避免多次调用SetUpdateState的【惨案】发生,下面是VCL中

    一段利用这个机制的源码,这种情况下,即便多次嵌套,逻辑也是非常清晰的,只有所有潜逃层结束时,计数器才能为0,保证了执行次数。

    procedure TStrings.AddStrings(const Strings: TArray<string>);  //由于这个过程会不断的引起Strings的滚动,带来批量的更新效果,所以这里作特别处理
    var
    I: Integer;
    begin
    BeginUpdate; //滚动之前增加开始标记
    try
           for I := Low(Strings) to High(Strings) do
             Add(Strings[I]);
    finally
         EndUpdate; //滚动之后作结束标记
    end;
    end;

    同样的设计,几乎贯穿了整个VCL控件处理,例如对数据集状态的控制,核心语句:TDataSet.BeginInsertAppend; 和 TDataSet.EndInsertAppend;

    DoBeforeInsert,DoBeforeScroll在BeginXXX部分实现; DoAfterInsert,DoAfterScroll在EndXXX部分实现。

    而各种新增操作,都以这个BeginXXX...EndXXX限定,如:TDataSet.AddRecord,TDataSet.Insert,TDataSet.Append,

    避免了事件的重复调用。当然这个事例中的计数器就更为复杂些,但道理是一样的。

    b,那么我们应该如何从VCL中汲取精华,进行我们的设计呢?同样的,我们还是以更新一个单据列表List,并避免滚动中出现重复刷新为例:

    type

    TForm1 =Class(TForm)

         private
              FiIndexListBusyCount : Integer; //数据集滚动计数器,由于没必要查看状态,所以开放为只读属性省了。
        public
             procedure BeginScroll; //防止多次滚动
             procedure EndScroll;

             procedure RefreshBill(ABillNo : string);  //刷新右边单据区域数据

             procedure RefreshList;                           //刷新左边的单据列表

    ...  //以下是实现部分
    procedure TForm1 .BeginScroll;  //由于只需要在滚动结束后,再做刷新动作,所以这里省去了滚动前预处理动作。
    begin
         Inc(FiIndexListBusyCount);     //计数器加1
    end;

    procedure TForm1 .EndScroll;
    begin
         Dec(FiIndexListBusyCount);
         if FiIndexListBusyCount = 0 then
              RefreshBill(CurrentBill);
    end;

    procedure TForm1 .ListAfterScroll(DataSet: TDataSet);  //这里引用一个事件来增加理解
    begin
        BeginScroll;
        EndScroll;  //在这里就有可能调用刷新单据内容的过程RefreshBill
    end;

    procedure TForm1 .RefreshBill(ABillNo : string);

    begin

    .... //这里是刷新某条单据内容的语句,例如QueryBill

    end;

    procedure TForm1 .RefreshList;

    begin

    BeginScroll;
    try
         ...这里做实际刷新List的动作,例如QueryList。这个动作会引起多次ListAfterScroll事件
    finally
         EndScroll;
    end;

    end;

    以上代码就完美的解决了滚动数据多次刷新的问题,并且非常好扩展。

  • 相关阅读:
    Begin Example with Override Encoded SOAP XML Serialization
    State Machine Terminology
    How to: Specify an Alternate Element Name for an XML Stream
    How to: Publish Metadata for a WCF Service.(What is the Metadata Exchange Endpoint purpose.)
    Beginning Guide With Controlling XML Serialization Using Attributes(XmlSerializaiton of Array)
    Workflow 4.0 Hosting Extensions
    What can we do in the CacheMetaData Method of Activity
    How and Why to use the System.servicemodel.MessageParameterAttribute in WCF
    How to: Begin Sample with Serialization and Deserialization an Object
    A Test WCF Service without anything of config.
  • 原文地址:https://www.cnblogs.com/Murphieston/p/9928218.html
Copyright © 2011-2022 走看看