删除运行时动态创建的按钮

时间:2017-02-01 14:53:54

标签: delphi

我需要用户能够右键单击该按钮并删除自身,但以下代码无法正常工作

        procedure TForm1.Button1Click(Sender: TObject);         ////////Creates a new object
        var
        ExampleButton : TButton;
        Begin
            ExampleButton := TButton.Create(self);  //Creates an object the same as its self
            ExampleButton.Parent := self;
//Button properties go here

//Procedures called here            
            ExampleButton.OnMouseDown := DragOrDelete;                          

        end;

上方创建按钮,下面我尝试删除它

procedure TForm1.DragOrDelete(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin

  CursorPosition := Mouse.CursorPos;          ////Location of mouse
  ExampleButtonStartingLocation := TPoint.Create((Sender as Tbutton).Left, (Sender as Tbutton).Top);
  if Button = mbRight then
  FreeAndNil(TButton);
end;

我得到的错误是常量对象不能作为var参数传递。 是因为我创建了许多TButton,但程序不知道哪一个引用它。

2 个答案:

答案 0 :(得分:2)

那么,

FreeAndNil(TButton);

应该是

(Sender as TButton).Free;  // thanks to DH

但这并不好。调用事件处理程序的RTL例程仍将具有对该按钮的引用,并且需要在事件处理程序退出后继续访问它,因此释放它可能会导致进一步的问题(同样,Sender不是{{1 }}参数,因此将其设置为var对调用者没有影响。)

更好的选择可能是创建nil作为Sender的自定义消息并将其发布到主表单。

修改

要执行此操作,您需要创建用户消息,例如

wParam

并用

替换违规行
const
  WM_DELETE_CONTROL = WM_USER +1;

然后在主窗体中创建一个处理消息的过程,例如

PostMessage( FormMain.WindowHandle, WM_DELETE_CONTROL, WPARAM( Sender ), 0 );

的定义如

procedure DestroyButton( var Msg : TMessage); message WM_DELETE_CONTROL;

答案 1 :(得分:0)

您应该在那里进行两项更改。

1)你应该记住你在一个变量中创建了哪个对象,它的生存时间足够长,两个程序都可以访问它。

2)你应该通过上面提到的变量销毁那个对象

现在你正试图摧毁一些随机按钮。但这不是你需要的!你可能想要完全破坏你正在创建的按钮,而不是另一个按钮。

所以:

1)FTableButton应移出procedure TForm1.Button1Click并升级为TForm1类的变量。

2)procedure TForm1.Button1Click应该检查按钮是否已经创建,而不是创建第二个,第三个,第四个...按钮。

procedure TForm1.Button1Click(Sender: TObject);      
var
  TableString : String;
Begin
   if nil <> Self.FTableButton then
      raise Exception.Create('Dynamic button already exists!!!');

    TableString := IntToStr(TableNumber);
    Self.FTableButton := TButton.Create(self);   

....

或者,您可以选择在创建新按钮之前删除现有按钮(如果有)。通常这不是一个好主意,但在某些特定情况下它可能是有意义的(当你需要&#34;重置&#34;按钮对象,删除旧的自定义对象并创建一个具有非自定义默认属性的新对象)。

procedure TForm1.Button1Click(Sender: TObject);      
var
  TableString : String;
Begin
  Self.FTableButton.Free; // if there already was a dynamic button - destroy it

  TableString := IntToStr(TableNumber);
  Self.FTableButton := TButton.Create(self);   

....

3)现在您已经在表单FTableButton变量中记住了该特定按钮,您可以使用它来删除该特定对象。

procedure TForm1.ButtonMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
...
  if Button = mbRight then
     FreeAndNil( Self.FTableButton );
end;

现在还有另一个潜在的问题 - &#34;自杀后的清理&#34;问题。

David Heffernan在评论中指出,您无法从自己的事件处理程序中删除TButton。 David说,退出TForm1.ButtonMouseDown过程后,刚删除的按钮会(或者至少可能会)自己做一些更多的事件后操作而不会认识到它已被删除,导致任何潜在的错误,很可能是Access违反nel dereference类。

我同意这一说法的一半。

1)使用来自VCL库for Windows的TFormTButton是安全的,因为VCL(至少在Delphi XE2版本中)经过精心设计以避免此陷阱。按钮的内部事件调用序列被设计为在TForm1.ButtonMouseDown退出后永远不会直接寻址自身。所以,至少在那种狭隘的情况下,大卫是不正确的,这是安全的事情。

2)这被称为&#34;依赖于实施细节&#34;然而。它很糟糕,因为它很脆弱。它可能在某些特定情况下起作用,但突然因许多其他原因而破裂。

一旦你从标准VCL TButton切换到任何其他库(Dev Express,LMD,TMS,任何东西)的其他一些花哨的按钮,我的代码突然崩溃了。或者您可以将VCL格式切换为FMX。或者从FMX / Win32切换到FMX / Android。

或许只是将Delphi升级到某个较新的版本会导致VCL在这方面中断(这种情况极不可能,但可能仍然存在)。

因此,为了安全起见,您必须解耦这两个操作:按钮的事件处理及其删除过程。有很多可能的方法,但它们都需要或多或少的额外工作,一些需要了解更多的问题。我认为这些方式分为两种途径。

1)时间不会改变,杀死按钮会立即生效。但它不应该是按钮删除本身,它应该是一些其他组件。

这是我最喜欢的方法。我认为右键单击删除按钮不是一个好主意。用户可能会错误地通过手的突然抽搐随意点击它。做一些像突然删除按钮一样极端的事情对于随机的不稳定动作来说会变得太苛刻。

我认为你应该采取传统方式。右键单击应打开上下文菜单,在菜单中应该有删除按钮的命令。这样,菜单将删除按钮,而不是按钮本身。

您需要将TPopupMenu添加到包含一个元素的表单上 - 删除该按钮。

  object mnu1: TPopupMenu
    object mniFreeBM: TMenuItem
      Caption = 'Free the button'
      OnClick = mniFreeBMClick
    end
  end

  procedure TForm18.mniFreeBMClick(Sender: TObject);
  begin
    FreeAndNil( Self.btnMenu );
  end;

然后你必须将该菜单连接到按钮。

.....
Self.FTableButton := TButton.Create(self);                        
//   FTableButton.OnMouseUp := ButtonMouseUp;   -- no more doing it! bad style!
Self.PopupMenu := mnu1;
mnu1.AutoPopup := True;
.....

我们走了。当用户R点击按钮时 - 会出现菜单,询问他是否要删除该按钮。如果他这样做 - 那么菜单,而不是按钮本身,将释放它。如果用户取消菜单 - 那么这是他的随机动作,他希望按钮保持活动状态。

这是我看到的更好的方法。

2)其他方法围绕时间延迟的想法,他们确保按钮要求 Delphi有一天删除,但是会有没有立即杀死。

当Delphi意识到它被要求这样做时,稍后会删除该按钮,在OnMouseUp程序之后(也可能很少有其他事件处理程序)被执行并退出。 如果应用程序没有重载looong heaaavy计算,那么&#34;稍后&#34;实际上非常短。很可能用户永远无法看到差异。为了安全起见,按钮可能会使自己隐身,直到有人将其删除。

可以有很多方法来做,但我会列出两个。

2.1)DSM在他的答案中概述了以Post_Message为中心的方法。那是一所优秀的老学校&#34;码。它很快。这是很好理解的。但它也有一些局限性。

a)它只适用于Windows。忘记Android,iOS等。好吧,如果您只打算在Windows上工作,那么您可以使用标准的VCL按钮,这些按钮可以安全自杀,至少在Delphi XE2中是这样。

b)它需要你制作大量的锅炉板,比如声明额外的程序和常量。

c)它是不安全的 - 你必须在整数和按钮指针之间进行严格的未经检查的类型转换。打字/重构很容易搞错。

d)你需要了解Windows实现:消息循环,VCL位于该消息循环内,PostMessage,SendMessage和Perform之间的区别等。

这不是火箭科学。对于任何&#34;旧学校&#34;桌面程序员很容易和众所周知。好吧,如果你是一个,你就不会问这样的问题。

另一种方法是使用多线程。从表现完美主义的角度来看是令人憎恶的。创建一个新线程(非常昂贵的操作!)只是为了回调并要求删除按钮 - 效率非常低。但是 - 这样你编写的代码就少得多。您可以使用标准的Delphi功能,这些功能最适用于当前和未来的Delphi版本中的每个操作系统和每个表单/按钮库。

代码就是这样。

procedure TForm18.btnThreadMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  ......
  if mbRight = Button then
     TThread.CreateAnonymousThread( procedure
       begin
         TThread.CurrentThread.Synchronize( nil, procedure
           begin
             FreeAndNil( btnThread );
           end
         );
       end
     ).Start;
end;    

这样当你退出OnMouseButtonUp按钮没有被删除时,只有来自临时线程的传入请求才能删除它。当表单工作时,请求可能会有所不同,但无论如何,这将是您安全退出按钮的事件处理程序后发生的另一个事件。 除非你使用了另一种憎恶ProcessMessages,但你没有,希望你永远不会。