讲述如何开发一个控件,很有价值(四)

博客围绕富文本编辑控件展开,先介绍利用Edit1控件显示变量结果,对RichEdit文本进行操作实验。接着尝试选择当前行文本显示在Edit2中,处理相关问题。还探讨改变文本格式,如让长行变红。最后指出PasCon返回RTF代码的问题,用OOP思想优化代码。

To start with I used the Edit1 Control to display the results of all these variables. I then tried manipulating text in the RichEdit to see what values I got. You should do the same. Type slowly in:

1234567890<CR>1234567890<CR>1234567890

and see how the results are reflected in the Edit control as you do so. Then experiment - try adding stuff to the ends of lines, and in the beginning of the line, and middle of lines. You may have to refer back to the Code to work out which number represents which variable.

Okay, now using the variables we have, lets try selecting the text of the current line, and display it in a new Edit Control (Edit2).

Add the following code to see what happens (don’t forget to add the second edit control and make it as wide as possible):

  MyRe.SelStart    := BeginSelStart;


  MyRe.SelLength   := EndSelStart - BeginSelStart;
  Edit2.Text       := MyRe.SelText;
end;

Run the program and try it out.

OOPS - That doesn't work - the text remains selected and the original cursor position is lost.
We need to reset SelStart and SelLength before we finish in the [OnChange] event. So let’s add at the end:

  MyRe.SelStart    := WasSelStart; 

     //back to were we started

  MyRe.SelLength   := 0;                  // nothing selected
end;

While playing with text in the edit control I discovered something weird.

If you typed [1] then <CR> then [2] the Edit1 displayed [4-1-3-4].

But there were only two characters in the display.

I made a mistake. It appears that RichEdit.Text can tell you where the beginning and end of line is. Why? Because you can access the <CR><LF> characters in the Text string. So we could have manipulated the Text property of the control to work out the beginning and end of lines by reading back and forward from SelStart to find <CR><LF> characters. We may not have known which line we were on, but we would know where it began and ended. Nevertheless we should keep this in mind, it might come in handy later.

But it doesn't matter - the EM_###### messages are a neat way of doing things. And they work. For the moment at least we'll stick with them.

7. Okay implement: Part 2 - Change the format

After the line Edit2.Text := MyRe.SelText, but before the "resetting" part, lets put some logic in to turn lines RED when they are longer than a certain length:

if (MyRe.SelLength > 10) then MyRe.SelAttributes.Color := clRed;

You'll notice two things if you test this out. First - it does work. Second however, is that if you type a line > 10 characters, press return and type one character - its in Red. This is because it inherits the Attributes of the preceding text. Just like if you have bold on in a Word processor, it doesn't reset if you press return. So lets change the line to include an else situation:

else MyRe.SelAttributes.Color := clBlack;

That seems to work - except when you press return in the middle of a > 10 character line you have already typed (which is already Red) to leave a stump < 10 characters on the line above - it remains red. This is because the code leaves you on the next line, and SelStart refers to this new line, not the previous one. In our eventual code, we'll have to take care to ensure this doesn't happen - we have to catch this situation and deal with it. It wont be the only situation I'm sure....

PS: There will be a number of situation we're we'll have to be careful. Can you think of any now? Try putting a lot of text in the Control (or manipulate a loaded file) and selecting some and using the inherit Drag and Drop (move your Mouse over some selected text, press and hold down the Left MouseButton and then drag away) to move some text. This only Triggers one OnChange Event. We may also be moving multiple lines along the way. In the future we'll have to put in some code to detect this happening, and ensure the [OnChange] event can deal with the need to reformat in two different locations. That means thinking in the back of the head about how in the future we may have to deal with this kind of situation, and ensure our code to deal with the simple situation can be adapted - i.e. be "versatile".

8. Basically it all seems to kind-of work.. can't we do some real programming now?

Okay, okay. But first we have a problem. Actually a rather big problem. The problem is PasCon. Why?

First: It returns RTF code.
Problem: We can't use RTF code.

Second: its designed to work an entire stream, and then give it back to us again as a whole.
Problem: We actually want greater control over it than this "all or nothing" approach.
 
 

OOP to the Rescue
 

When you have something that works in a situation, and needs to be applied in another situation were it has to do a similar, but subtly different job - you have two choices:

  1. copy the function, and re-write it for the new situation, or
  2. kludge around it (e.g use Pas2Rtf, and then write a RtfCodes2RtfControl procedure).

Modern languages however give you an option: OOP it. "Objectify" it. This is more than just deriving something from an existing object. It is in a sense programming in a "state of mind". Controls should be created so they can be used in a variety of situations - father than situation specific. In this case all PasCon can deal with is tokenising the input stream and returning code RTF text. What we really need to do is divide it into two entitites. We need to separate the [Parsing/Recognise the Token and TokenType] from the [Encode it in RTF codes].

So lets start with ConvertReadStream, editing it so it looks something like this:
 

function TPasConversion.ConvertReadStream: Integer;
begin

    FOutBuffSize := size+3;
    ReAllocMem(FOutBuff, FOutBuffSize);
    FTokenState := tsUnknown;
    FComment    := csNo;
    FBuffPos    := 0;
    FReadBuff   := Memory;

    {Write leading RTF}

    WriteToBuffer('{/rtf1/ansi/deff0/deftab720');
    WriteFontTable;
    WriteColorTable; 
   WriteToBuffer('/deflang1033/pard/plain/f2/fs20 ');

    Result:= Read(FReadBuff^, Size);

    if Result > 0 then
    begin

        FReadBuff[Result] := #0;
        Run := FReadBuff;

        while Run^ <> #0 do
        begin

         Run := GetToken(Run,FTokenState,TokenStr);
         ScanForRTF;
         SetRTF;
         WriteToBuffer(PreFix + TokenStr + PostFix);

        end;

        {Write ending RTF}

        WriteToBuffer(#13+#10+'/par }{'+#13+#10);

    end;

    Clear;

    SetPointer(FOutBuff, fBuffPos-1) ;

end; { ConvertReadStream }

The code for ConvertReadStream is now much smaller, and also easier to understand. We can then take all the code that used to be in ConvertReadStream that did the tokenizing and create a new subroutine - the GetToken function that just does the recognizing and labelling of the individual tokens. In the process we also loose a huge number of repeated lines of code, as well as a number of sub-routines such as HandleBorCom and HandleString.
 

//
// My Get Token routine
//

function TPasConversion.GetToken(Run: PChar; var aTokenState: TTokenState; 
var aTokenStr: string):PChar;
begin

    aTokenState := tsUnknown;
    aTokenStr := '';
    TokenPtr := Run;         // Mark were we started

    Case Run^ of

#13

:

begin

aTokenState := tsCRLF;


inc(Run, 2);

end;

#1..#9, #11, #12, #14..#32:

begin

while Run^ in [#1..#9, #11, #12, #14..#32] do inc(Run);


aTokenState:= tsSpace;

end;

'A'..'Z', 'a'..'z', '_':

begin

aTokenState:= tsIdentifier;


inc(Run);
while Run^ in ['A'..'Z', 'a'..'z', '0'..'9', '_'] do inc(Run);
TokenLen:= Run - TokenPtr;
SetString(aTokenStr, TokenPtr, TokenLen);

if IsKeyWord(aTokenStr) then


begin

if IsDirective(aTokenStr) then aTokenState:= tsDirective


else aTokenState:= tsKeyWord;

end;

end;

'0'..'9':

begin

inc(Run);


aTokenState:= tsNumber;
while Run^ in ['0'..'9', '.', 'e', 'E'] do inc(Run);

end;

'{':

begin

FComment := csBor;


aTokenState := tsComment;
while not ((Run^ = '}') or (Run^ = #0)) do inc(Run);
inc(Run);

end;

'!','"', '%', '&', '('..'/', ':'..'@', '['..'^', '`', '~' :

begin

aTokenState:= tsUnknown;


while Run^ in ['!','"', '%', '&', '('..'/', ':'..'@', '['..'^',
'`', '~'] do
begin

Case Run^ of

'/': 


if (Run + 1)^ = '/' then
begin

if (aTokenState = tsUnknown) then


begin

while (Run^ <> #13) and (Run^ <> #0) do inc(Run);


FComment:= csSlashes;
aTokenState := tsComment;
break;

end

else

begin

aTokenState := tsSymbol;


break;

end;

end;

'(': 


if (Run + 1)^ = '*' then
begin

if (aTokenState = tsUnknown) then


begin

while (Run^ <> #0) and not ( (Run^ = ')') and ((Run - 1)^ = '*') ) do inc(Run);

FComment:= csAnsi;
aTokenState := tsComment;

inc(Run);
break;

end

else

begin

aTokenState := tsSymbol;


break;

end; 

end;

end;

aTokenState := tsSymbol; inc(Run); 

end; 

if aTokenState = tsUnknown then aTokenState := tsSymbol; 

end;

#39:

begin

aTokenState:= tsString;


FComment:= csNo;

repeat 

Case Run^ of


         #0, #10, #13: raise exception.Create('Invalid string');
end;

inc(Run);

until Run^ = #39;

inc(Run);

end;

'#':

begin

aTokenState:= tsString;


while Run^ in ['#', '0'..'9'] do inc(Run);

end;

'$':

begin

FTokenState:= tsNumber;


while Run^ in ['$','0'..'9', 'A'..'F', 'a'..'f'] do inc(Run);

end;

else

if Run^ <> #0 then inc(Run);

    end;

    TokenLen := Run - TokenPtr;
    SetString(aTokenStr, TokenPtr, TokenLen);
    Result := Run

end; { ConvertReadStream }

内容概要:本文研究了一种基于短时傅里叶变换(STFT)结合卷积神经网络(CNN)与残差网络(ResNet)的故障诊断方法,并提供了Matlab代码实现。该方法首先利用STFT将一维时域振动信号转换为二维时频图,以直观呈现信号的频率随时间变化特征;随后构建CNN-ResNet深度学习模型,通过卷积层自动提取故障相关的深层特征,并利用ResNet的残差结构缓解深层网络训练中的梯度消失问题,提升模型收敛速度与诊断精度。整个流程实现了端到端的故障识别,适用于轴承、齿轮箱等机械设备的智能故障诊断。; 适合人群:具备一定信号处理基础和Matlab编程能力,从事机械故障诊断基于短时傅里叶变换(STFT)结合卷积神经网络(CNN)和残差网络(ResNet)的故障诊断研究(Matlab代码实现)、工业自动化或智能制造方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于旋转机械系统的状态监测与早期故障预警;②用于学术研究中对比不同深度学习模型在故障诊断中的性能差异;③作为智能运维系统的核心算法模块,提升设备维护效率与可靠性。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,深入理解STFT时频分析原理与CNN-ResNet网络架构设计,同时可尝试在公开数据集(如CWRU轴承数据集)上验证模型效果,并进一步探索其他时频分析方法与网络结构的融合优化。
【源码免费下载链接】:https://renmaiwang.cn/s/h82is Sentinel是Redis的一个关键组件,主要提供稳定性方案以保证集群的高可用性(HA)。在Redis集群中,Sentinel负责监控主节点和从节点的状态,当主节点出现故障时,它会自动启动故障转移机制。具体来说,在检测到主节点故障后,Sentinel会将一个从节点提升为主节点,并指导其他从节点重新配置为新的辅助角色。这种机制确保即使在主节点出现问题的情况下,服务仍能持续运行,从而显著提升了系统的可靠性。对于Redis集群的稳定运行而言,集群配置文件至关重要,因为它包含了Sentinel执行监控、故障判定和转移操作所依据的核心规则。 以下是对Redis Sentinel集群配置文件的关键知识点进行了详细说明: 1. **基础设置项**: - `port`:指定每个Sentinel实例监听的具体端口号。 - `bind`:定义Sentinel的监听IP地址,允许多个地址选择。 - `sentinel monitor`:配置主节点监控规则,格式为`sentinel monitor <master-name> <ip> <port> <quorum>`。其中,`master-name`为主节点别名,`ip`和`port`为主节点的IP及端口号,而`quorum`表示系统需要达到多少个Sentinel同意主节点出现故障才会启动转移。 2. **故障判定机制**: - `quorum`:决定系统在何种情况下触发故障转移。通常设置为总Sentinel数量的大头。 - `down-after-milliseconds`:设定主节点失联的时间阈值,超过此时间将视为节点失效。 3. **故障转移操作**: - `sentinel平行查询安全因子`:(`sentinel parallel-syncs`) 表示在执
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值