Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


Extending Context Menu of Data Dissect Structure Entry
Goto page Previous  1, 2
 
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Lua Scripting
View previous topic :: View next topic  
Author Message
ParkourPenguin
I post too much
Reputation: 152

Joined: 06 Jul 2014
Posts: 4706

PostPosted: Sat Sep 21, 2024 3:02 pm    Post subject: Reply with quote

registerFormAddNotification gets called for every form that gets newly created. If you don't know what it does, play around with it and see what happens:
Code:
registerFormAddNotification(function(f)
  print(f.ClassName)
end)
Execute this then open a bunch of windows in CE. e.g. a structure dissect window

The first part of that code I stole and modified from DB's post is a helper function:
Code:
function forEachAndFutureForm(classname, func)
  local i
  for i=0,getFormCount()-1 do
    local f = getForm(i)
    if f.ClassName==classname then
      func(f)
    end
  end

  registerFormAddNotification(function(f)
    if classname==f.ClassName then
      f.registerFirstShowCallback(func)
    end
  end)
end
The function's name, "forEachAndFutureForm", describes what it does. It takes two parameters: a class name and a function that takes a form. The provided function will be executed once for every form with the specified class name- both currently visible forms and any forms that get created in the future.
The for loop iterates over the currently visible forms and executes the function for each form that already exists.
The call to `registerFormAddNotification` will make sure CE calls the provided function whenever a new form is created. `registerFirstShowCallback` makes sure everything in the form has been initialized before the function gets called- e.g. the popup menu might not exist when the `registerFormAddNotification` callback is executed, but it will when the `registerFirstShowCallback` callback is executed.

The second part calls that helper function:
Code:
forEachAndFutureForm('TfrmStructures2',function(f)
  local mi = createMenuItem(f.pmStructureView)
  mi.Name = 'miHelloWorld'
  mi.Caption = 'Hello'
  mi.OnClick = function() print'Hello' end

  f.pmStructureView.Items.add(mi)
end)
The class name of the form you want to modify is "TfrmStructures2". The callback function takes the form and adds a new item to the popup menu.

You can probably figure out the class name by opening the window and iterating over all the current forms:
Code:
for i = 0, getFormCount() - 1 do
  print(getForm(i).ClassName)
end

To get the name of the popup menu, iterate over all the owned components of the form:
Code:
local structfrm
for i = 0, getFormCount() - 1 do
  local f = getForm(i)
  if f.ClassName == 'TfrmStructures2' then
    structfrm = f
    break
  end
end

for i = 0, structfrm.ComponentCount - 1 do
  local c = structfrm.Component[i]
  print(c.ClassName, '\t', c.Name, '\t', c.Caption)
end

If you have CE's source code readily available, it might be faster to look in the relevant lfm file. e.g. Cheat Engine/StructuresFrm2.lfm:
Code:
object frmStructures2: TfrmStructures2
  ...
  object pmStructureView: TPopupMenu
    Images = sdImageList
    OnPopup = pmStructureViewPopup
    Left = 48
    Top = 120
    object miChangeValue: TMenuItem
      Caption = 'Change value'
      ImageIndex = 5
      ShortCut = 13
      OnClick = miChangeValueClick
    end
    ...

_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
Csimbi
I post too much
Reputation: 97

Joined: 14 Jul 2007
Posts: 3327

PostPosted: Sun Sep 22, 2024 8:50 am    Post subject: Reply with quote

ParkourPenguin wrote:
...

Thanks for the detailed explanation.

Let me check if I got this right:
- that timer-based registration I posted earlier was the wrong way to go.
- the right way to go is forEachAndFutureForm because that event will fire every time a new instance of a form is created.
- forEachAndFutureForm must interate through all forms and call the function passed as a parameter when the right form is found (which, in my case is fn_AddPopupMenuItems).

Questions (please forgive my ignorance again):
- Why do I have to scan all forms using forEachAndFutureForm and re-register each form? Won't that cause issues in the long run without unregistering potentially existing registrations?
- What happens when another script includes forEachAndFutureForm for the same form, albeit with a different function?
- Wouldn't it be more logical to have a PostCreate() event that fires once for every new instance of the form, after it has been instantiated and fully initialized just before it would be displayed?
Back to top
View user's profile Send private message
ParkourPenguin
I post too much
Reputation: 152

Joined: 06 Jul 2014
Posts: 4706

PostPosted: Sun Sep 22, 2024 9:57 am    Post subject: Reply with quote

Csimbi wrote:
the right way to go is forEachAndFutureForm because that event will fire every time a new instance of a form is created
`forEachAndFutureForm` is just a helper function DB made up. You can call it whatever you want. The important bits are CE's API- particularly `registerFormAddNotification` and `registerFirstShowCallback`.

Csimbi wrote:
Why do I have to scan all forms using forEachAndFutureForm and re-register each form? Won't that cause issues in the long run without unregistering potentially existing registrations?
`registerFormAddNotification` only triggers for new forms. It doesn't trigger for forms that already exist. e.g. if you already had a structure dissect window open and execute that code without the `for` loop, any new structure dissect windows would be modified, but the one that was already opened wouldn't.

I'm not sure what you mean by "re-register each form". If you call that function more than once, then the old `registerFormAddNotification` callback(s) will still be run. You can modify the function a little to be able to unregister it:
Code:
function forEachAndFutureForm(classname, func)
  for i=0,getFormCount()-1 do
    local f = getForm(i)
    if f.ClassName==classname then
      func(f)
    end
  end

  return registerFormAddNotification(function(f)
    if classname==f.ClassName then
      f.registerFirstShowCallback(func)
    end
  end)
end

if structDissectPrintHelloCallback then
  unregisterFormAddNotification(structDissectPrintHelloCallback)
  structDissectPrintHelloCallback = nil
end

structDissectPrintHelloCallback = forEachAndFutureForm('TfrmStructures2', function(f)
  if f.pmStructureView.miHelloWorld then
    -- might run again if any windows are opened
    f.pmStructureView.miHelloWorld.destroy()
  end

  local mi = createMenuItem(f.pmStructureView)
  mi.Name = 'miHelloWorld'
  mi.Caption = 'Hello'
  mi.OnClick = function() print'Hello' end

  f.pmStructureView.Items.add(mi)
end)
Try modifying the message printed and execute the script again.
NB: when this code is run multiple times, the `for` loop might execute your function on a window that a previous callback had already run on. This is why this callback now destroys the menu item created by any previous callback.

Csimbi wrote:
What happens when another script includes forEachAndFutureForm for the same form, albeit with a different function?
Both of the callback functions get run.
Again, it's fine to play around with Lua yourself and see what happens:
Code:
...
forEachAndFutureForm('TfrmStructures2', function(f)
  print'Hello'
end)
forEachAndFutureForm('TfrmStructures2', function(f)
  print'World'
end)

Csimbi wrote:
Wouldn't it be more logical to have a PostCreate() event that fires once for every new instance of the form, after it has been instantiated and fully initialized just before it would be displayed?
That's exactly what `registerFirstShowCallback` is. It registers a callback function to be run when the form is first shown. This is better than setting a single callback function for an event (e.g. Timer.OnTimer) because several different functions can be registered without overriding each other (see previous code block).
_________________
I don't know where I'm going, but I'll figure it out when I get there.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Lua Scripting All times are GMT - 6 Hours
Goto page Previous  1, 2
Page 2 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites