MVC pattern example

Put simple Tips and Tricks that are not entire Tutorials in this forum
Gotjek
Posts: 18
Joined: 11 Aug 2022, 11:01

MVC pattern example

Post by Gotjek » 03 Oct 2022, 08:33

Hi,

I like MVC pattern to prevent my GUI scripts from becoming unmanageable too quickly.
I found great tutorials here but I lacked a functional sample in v2.
Here is my attempt. I am not very experienced and this interpretation of MVC maybe isn't canonic.
But I struggled some hours to do this and it could help you.

Code: Select all

Class App_Infos {
    static name := "The Application"
}


Class Something_Model {

    __New(*) {
        this.button_times_clicked := 0
    }
}


Class Something_View Extends Gui {

    GUI_WIDTH := 400

    __New(controller_arg, win_title) {
        super.__New(, Title := win_title)
        super.Opt("+OwnDialogs")

        this.controller := controller_arg

        this.nice_btn := this.Make_Nice_Button()

        super.Show("w" this.GUI_WIDTH)
        super.OnEvent("Close", this.Close_View)
        super.OnEvent("Escape", this.Close_View)
    }

    Make_Nice_Button() {
        control := super.Add("Button", , "I'm a nice button")
        control.OnEvent("Click",this.Click_Nice_Button.Bind(this))
        Return control
    }

    Click_Nice_Button(*) {
        this.controller.Click_Nice_Button()
    }

    Close_View(*) {
        this.controller.Close_View()
    }
}


Class Something_Controller {

    __New(model_arg) {
        this.model := model_arg
        this.view := Something_View(this, App_Infos.name)
        this.is_done := false
    }

    Click_Nice_Button() {
        this.model.button_times_clicked += 1
        times := this.model.button_times_clicked
        MsgBox "You clicked me " times " time(s) !"
    }

    Close_View() {
        this.is_done := true
        this.view.Destroy()
    }
}


my_model := Something_Model()
my_app := Something_Controller(my_model)

While not my_app.is_done {  ; Waits for the View to be closed.
}

times := my_model.button_times_clicked
MsgBox "This is done !`nThe nice button was clicked " times " time(s) !"

guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: MVC pattern example

Post by guest3456 » 04 Oct 2022, 09:02

can you give a quick explanation of the MVC pattern and its pros/cons and why you prefer it?


Gotjek
Posts: 18
Joined: 11 Aug 2022, 11:01

Re: MVC pattern example

Post by Gotjek » 05 Oct 2022, 06:34

Unfortunately, I don't feel experienced enough to explain this properly. I'm not even very sure I have well understood MVC.

I'm trying to make small apps with GUI for my work and I'm not a developer at all. I learned Python as an amateur and I used to code with it. I always ended up creating code that seemed very unclear to me after 1000 lines. I searched how to structure my code and I came across some blogs/articles that talked about MVC. It's about separating the data, the interface, and the logic that ties the two together.

I like this idea because it allows me to code in isolated pieces. Once I have coded a piece, for example the interface, I almost never go back to it. In practice, I don't always know which actions to put in which parts (model, view or controller). But this technique reassures me.

Pros :
+ Feeling of having a structured, solid code.
+ Obligation to choose on which responsibility the code that I will write depends (M, V, or C).
+ View and Model are reusable.

Cons
- Cumbersome to bind view events to controller.
- You have to think about "this" constantly.
- Certainly not suitable for all projects.

I used resources like this in Python :
- https://wiki.wxpython.org/ModelViewController
In the forum :
- viewtopic.php?t=58077

dkzeanah
Posts: 6
Joined: 24 Apr 2022, 03:23

Re: MVC pattern example

Post by dkzeanah » 03 Jan 2023, 16:59

Upon initial review, I'm really liking this. It seems fairly complex and produces clear and simple to understand functionality and am hesitant to consider your "beginner"-like claims :lol: .

One problem I have I have is jumping into writting code and quickly have scripts that are too large and muddled, with which I quickly get lost in code I myself wrote. It seems like this produces an effective way to mitigate this and hopefully I will find out for sure as I have time to work more using this template code :D Thanks!

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: MVC pattern example

Post by iPhilip » 19 Jan 2023, 20:22

Hi @Gotjek, Thank you for sharing this example. I am intrigued about using the MVC methodology in future AutoHotkey v2 scripts.

I took the liberty of rewriting your example to create crisper boundaries between the 3 roles. In the end, it's a bit more compact. See below.

Code: Select all

class Model
{
   __New() {
      this.times_clicked_button := 0
      this.gui_title := "The Application"
      this.gui_width := 250
   }
}

class View extends Gui
{
   __New(controller) {
      super.__New("-MinimizeBox -MaximizeBox", controller.model.gui_title)
      
      button := super.Add("Button", "x100 Default", "My Button")
      button.OnEvent("Click", ObjBindMethod(controller, "Button_Click"))
      
      super.Show("w" controller.model.gui_width)
      super.OnEvent("Close",  ObjBindMethod(controller, "Gui_Close"))
      super.OnEvent("Escape", ObjBindMethod(controller, "Gui_Close"))
   }
}

class Controller
{
   __New(model) {
      this.model := model
   }
   
   Button_Click(GuiCtrl, Info) {
      GuiCtrl.Gui.Opt("+OwnDialogs")
      MsgBox "You now have clicked me " (++this.model.times_clicked_button) " time(s)."
   }
   
   Gui_Close(Gui) {
      Gui.Destroy()
      MsgBox "The button was clicked " this.model.times_clicked_button " time(s) in total."
   }
}

View(Controller(Model()))
Cheers!
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Gotjek
Posts: 18
Joined: 11 Aug 2022, 11:01

Re: MVC pattern example

Post by Gotjek » 25 Jan 2023, 11:44

@iPhilip : Great !

User avatar
andymbody
Posts: 856
Joined: 02 Jul 2017, 23:47

Re: MVC pattern example

Post by andymbody » 25 Jan 2023, 12:35

guest3456 wrote:
04 Oct 2022, 09:02
can you give a quick explanation of the MVC pattern and its pros/cons and why you prefer it?
There are lots of info online about MVC. This concept is used a lot behind the scenes and basically separates data storage (model) from the implementation of that data (view). The controller portion is used to interact with the data and view, so they do not interact directly with each other.

Think of the controller as the program that pulls the data from the database (model) and pushes it (in particular format) to be consumed in some way (view). The view is simply the output of that data in a particular format (not always a monitor). The View could be an output file, a print from a printer, input to another database, etc.

So for example, your computer uses MVC. The data (harddrive) never interacts directly with your monitor (view). The controller (hardware, operating system, etc) pulls data from the computer data storage (or a server) and displays on the monitor. The data has no knowledge of the existence of the monitor, and vice-versa.

You can do the same thing with web requests, and a million other dataStorage-to-dataUsage scenarios. On any scale - large, medium, small, micro. Or a combination/hybrid/multiples of all of them, within a single solution.

It's basically Input (model), conversion (controller), output (view).

Andy
Last edited by andymbody on 25 Jan 2023, 12:49, edited 1 time in total.

guest3456
Posts: 3453
Joined: 09 Oct 2013, 10:31

Re: MVC pattern example

Post by guest3456 » 25 Jan 2023, 12:48

andymbody wrote:
25 Jan 2023, 12:35
guest3456 wrote:
04 Oct 2022, 09:02
can you give a quick explanation of the MVC pattern and its pros/cons and why you prefer it?
There are lots of info online about MVC. This concept is used a lot behind the scenes and basically separates data storage (model) from the implementation of that data (view). The controller portion is used to interact with the data and view, so they do not interact directly with each other.

Think of the controller as the program that pulls the data from the database (model) and pushes it (in particular format) to be consumed in some way (view). The view is simply the output of that data in a particular format (not always a monitor). The View could be an output file, a print from a printer, input to another database, etc.

So for example, your computer uses MVC. The data (harddrive) never interacts directly with your monitor (view). The controller (hardware, operating system, etc) pulls data from the computer data storage (or a server) and displays on the monitor. The data has no knowledge of the existence of the monitor, and vice-versa.

You can do the same thing with web requests, and a million other dataStorage-to-dataUsage scenarios. On any scale - large, medium, small, micro. Or a combination/hybrid of all of them.

It's basically Input (model), conversion (controller), output (view).

Andy
excellent, thank you


User avatar
andymbody
Posts: 856
Joined: 02 Jul 2017, 23:47

Re: MVC pattern example

Post by andymbody » 25 Jan 2023, 13:27

A simple function has the concept of MVC built in.

Code: Select all

; these values are the model in this case (simulates a data source)
; (extenal data from the perspective of the function below)
; the local controller does not manage the data source
; the local controller only receives from the data source
; the data source usually has it's own MVC setup that manages it
a := 5
b := 10

; the function is the controller in this case
result := add(a,b)

; this is one possible view, from the perspective of an observer
; the local controller (add function) does not manage the view
; the msgbox function does this (separate from the add function)
Msgbox % result

; different view (same model and controller)
; also may become input to another model database of a separate MVC
; the file system manages the view in this case
Fileappend, %result%, %A_Desktop%\temp.txt

; function acting as a controller
; num1 and num2 are the input data (model)
add(num1, num2)
{
	 ; the view from perspective of function
	return num1 + num2
}

We use this concept a lot whether we realize it or not - usually instinctively. We even use it in our everyday lives (but we don't have an acronym for it).
Everyday examples of same concept
* baking a birthday cake from scratch, then presenting it to someone
* withdrawing money from the bank to purchase something
* buying flowers from a florist and giving them to your wife for valentines.

It's actually a simple concept that has been labeled with fancy geek jargon, which we should avoid getting hung up on.

Again... input, conversion, output

Andy

swyfty
Posts: 5
Joined: 06 Feb 2023, 14:50

Re: MVC pattern example

Post by swyfty » 09 Feb 2023, 19:10

I was following a tutorial on MVC with PyQT a long while back; does AHK have a similar signal/slot? to facilitate sending a notification to another class that's not one of the call parameters?

in the example above, View encapsulates Controller which encapsulates Model.

Simple example

Code: Select all

;This is a MVC model for a button with a cooldown progress bar you can change the icon
#include CoolDownTimer.ahk    ; separate class that handles the progress bar percent based on the "Tick" setTimer example using A_TickCount for accuracy
Class ButtonWProgressModel{
	__New(parentGui){
	this.icon := "C:\somepath\icon.png"
	this.cd := 800   ;cooldown in MS
	this.parentGui := parentGui
 	}
}
Class ButtonWProgressView extends Gui{
		__New(controller){
		this.Controller := controller
    		super.__New("-SysMenu +E0x08000000 -border +ToolWindow -0x800000 -Caption +AlwaysOnTop +Parent" . controller.model.parentGui.Hwnd)
		this.button := super.Add("Picture","w36 h36", controller.model.icon)
		this.pbar := super.Add("Progress", "x0 y36 w36 h10 cRed BackgroundGreen")
		this.cdt := CoolDownTimer(controller.model.cd, this.pbar)    
		this.button.OnEvent("Click", ObjBindMethod(this.cdt, "Start"))
		this.button.OnEvent("ContextMenu", ObjBindMethod(controller, "Button_Setup"))   
		}
}
Class ButtonWProgressController{
	__New(Model){
		this.model := model
	}
	Button_Setup(*){
        selectedIcon:= FileSelect(3,"./","Select An Icon","*.png; *.ico")
            if selectedIcon != ""{
                this.model.icon:= selectedIcon
                }  
        }
}

Class ButtonWProgress{
    __New(parentGui) {
        this.model := ButtonWProgressModel(parentGui)
        this.Controller := ButtonWProgressController(this.model)
        This.View := ButtonWProgressView(this.Controller)
        this.View.Show("x0 Y0 W38 H48")
    }
 }
parentTest := Gui("+AlwaysOnTop +Resize","A")
bwp := ButtonWProgress(parentTest)
parentTest.Show("W300 H300")

Say something makes a change to the model like the icon for a picture button via the controller, how do you programmatically tell the view to update the icon?

Heres the CoolDownTimer.ahk if you want to see what I am trying to do

Code: Select all

class CoolDownTimer {
    __New(coolDowninMS,progressBar) {
        this.CooldownMS := coolDowninMS
        this.step := 1
        this.interval := this.CooldownMS / 100
        this.pBar:= progressBar
        this.intervalCount := 0
        this.startMS := 0
        this.count := 0
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start(GuiCrl,info) {
        SetTimer this.timer, this.interval      

        this.pBar.Value := 100
        this.startMS := A_TickCount
    }
    Stop() {

        this.pBar.Value := 0
        SetTimer this.timer, 0
        this.intervalCount := 0
    }
    Tick() {

        this.count := A_TickCount - this.startMS
        this.pBar.Value := Round(100-(this.count/this.CooldownMS * 100))
        if this.count >= this.CooldownMS{
            this.stop
            }
    }
    SetCD(newTimeInMS){
        this.CooldownMS := newTimeInMS
    }
}

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: MVC pattern example

Post by iPhilip » 11 Feb 2023, 05:26

@swyfty, the way I think about what you are trying to do is as a "nested MVC" pattern, i.e. a "parent MVC" pattern with many "child MVC" patterns. See the code below:

Code: Select all

#include CoolDownTimer.ahk

class ParentModel {
    __New(w, h, title) {
        this.w := w
        this.h := h
        this.title := title
   }
}
class ParentController {
    __New(model) {
        this.model := model
    }
    Gui_Close(Gui) {
        Gui.Destroy()
    }
}
class ParentView extends Gui {
    __New(controller) {
        super.__New(, controller.model.title)
        super.Show("w" controller.model.w " h" controller.model.h)
        super.OnEvent("Close",  ObjBindMethod(controller, "Gui_Close"))
        super.OnEvent("Escape", ObjBindMethod(controller, "Gui_Close"))
    }
}

class ChildModel {
    __New(x, y, w, icon, parent) {
        this.x := x
        this.y := y
        this.w := w
        this.h := w
        this.bar := {h:10, time:800, color:"White", backColor:"Gray"}
        this.icon := {w:36, h:36, file:icon}
        this.parent := parent
    }
}
class ChildController {
    __New(model) {
        this.model := model
    }
    LoadIcon(GuiCtrl, *) {
        if icon := FileSelect(3, "./" , "Select an Icon", "*.png; *.ico")
            GuiCtrl.Value := this.model.icon.file := icon
   }
}
class ChildView extends Gui {
    __New(controller) {
        super.__New("-Border -Caption -SysMenu +ToolWindow -0x800000 +E0x08000000 +Parent" controller.model.parent.hwnd)
        super.MarginX := (controller.model.w - controller.model.icon.w) // 2
        super.MarginY := (controller.model.h - controller.model.icon.h - controller.model.bar.h) // 2
        pic := super.Add("Picture", "xm ym w" controller.model.icon.w " h" controller.model.icon.h, controller.model.icon.file)
        bar := super.Add("Progress", "xm y" (controller.model.h - controller.model.bar.h) " w" controller.model.icon.w " h" controller.model.bar.h " c" controller.model.bar.color " Background" controller.model.bar.backColor)
        super.Show("x" controller.model.x " y" controller.model.y " w" controller.model.w " h" controller.model.h)
        pic.OnEvent("Click", ObjBindMethod(CoolDownTimer(controller.model.bar.time, bar), "Start"))
        pic.OnEvent("ContextMenu", ObjBindMethod(controller, "LoadIcon"))
   }
}

ParentGui := ParentView(ParentController(ParentModel(300, 300, "Parent")))
ChildView(ChildController(ChildModel( 0,  0, 50, "Icon.ico", ParentGui)))
ChildView(ChildController(ChildModel(50,  0, 50, "Icon.ico", ParentGui)))
ChildView(ChildController(ChildModel( 0, 50, 50, "Icon.ico", ParentGui)))
ChildView(ChildController(ChildModel(50, 50, 50, "Icon.ico", ParentGui)))
Cheers!
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

swyfty
Posts: 5
Joined: 06 Feb 2023, 14:50

Re: MVC pattern example

Post by swyfty » 11 Feb 2023, 09:07

@iPhilip
:bravo:
So much cleaner than my code. Thank you...

After banging my head for a few hours, I did mod my code around to make controller own the view like in the original post.

But yours is what I intended to do, I just couldn't figure it out. I'll try to follow your pattern for new classes and go back and refactor the button later.

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: MVC pattern example

Post by iPhilip » 11 Feb 2023, 14:46

You are welcome. It gave me an opportunity to practice MVC patterns. :)
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

iPhilip
Posts: 791
Joined: 02 Oct 2013, 12:21

Re: MVC pattern example

Post by iPhilip » 12 Feb 2023, 14:42

I thought it might be useful to show a simple example where the Model communicates with the Viewer and/or the Controller since "MVC dictates that all user data be stored in the Model layer" [ref].

Code: Select all

class Model
{
   __New(MaxCount, MsgNumber := 0x5555) {
      this.max_count := MaxCount
      this.msg_number := MsgNumber
      this.gui_title := "MVC Example"
      this.gui_width := 250
      this.count := 0
      SetTimer ObjBindMethod(this, "Count_Monitor")
   }
   
   Count_Monitor() {
      if this.count = this.max_count {
         SetTimer , 0
         this.Post_Message()
      }
   }
   
   Post_Message(wParam := 0, lParam := 0) {
      SetTitleMatchMode 2
      DetectHiddenWindows true
      PostMessage this.msg_number, wParam, lParam, A_ScriptHwnd
      DetectHiddenWindows false
   }
}

class View extends Gui
{
   __New(controller) {
      super.__New("-MinimizeBox -MaximizeBox", controller.model.gui_title)
      
      this.button := super.Add("Button", "x100 Default", "My Button")
      this.button.OnEvent("Click", ObjBindMethod(controller, "Button_Click"))
      
      super.Show("w" controller.model.gui_width)
      super.OnEvent("Close",  ObjBindMethod(controller, "Gui_Close"))
      super.OnEvent("Escape", ObjBindMethod(controller, "Gui_Close"))
      
      OnMessage(controller.model.msg_number, ObjBindMethod(this, "Msg_Monitor"), -1)
   }
   
   Msg_Monitor(*) {
      this.button.Text := "Done"
      this.button.Enabled := false
   }
}

class Controller
{
   __New(model) {
      this.model := model
      OnMessage(this.model.msg_number, ObjBindMethod(this, "Msg_Monitor"))
   }
   
   Msg_Monitor(*) {
      MsgBox "The button was clicked the maximum number of times (" this.model.count ").`n`nThe script will now exit."
      ExitApp
   }
   
   Button_Click(GuiCtrl, Info) {
      GuiCtrl.Gui.Opt("+OwnDialogs")
      MsgBox "You now have clicked the button " (++this.model.count) " time(s)."
   }
   
   Gui_Close(Gui) {
      Gui.Opt("+OwnDialogs")
      MsgBox "The button was clicked " this.model.count " time(s) in total.`n`nThe script will now exit."
      ExitApp
   }
}

View(Controller(Model(3)))
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

swyfty
Posts: 5
Joined: 06 Feb 2023, 14:50

Re: MVC pattern example

Post by swyfty » 13 Feb 2023, 08:01

@iPhilip

At the risk of sounding pedantic (I'm still learning so, i'm totally willing to be wrong) I think we need to rethink a portion of this code if we're leaving examples under tutorials... As Ive been reading more, if we're to follow canonical MVC pattern, I believe that it mandates that the model zero knowledge of the view and vice versa.

Therefore, Gui parent should be passed in through the view class and not the model.

Here's where I think my MVC and my attempts OOP have gotten slightly out of control. I was told a while back that every class should have one job, it gets a bit ridiculous if you go too far down that rabbit hole, but I try to balance. Im still in the air as to whether or not the controller should instantiate the view and be the interface to the rest of the program, or should the view be the outer "wrapper" to the rest of the program.

Code: Select all

Controller(parent)
	this.model := model()
	view := View(this, parent)
vs.

Code: Select all

View(Controller(Model()),parent)

I guess the only difference would be, should we want the rest of the program to interact via the controller or the view?

User avatar
dd900
Posts: 121
Joined: 27 Oct 2013, 16:03

Re: MVC pattern example

Post by dd900 » 19 Feb 2023, 22:30

iPhilip wrote:
12 Feb 2023, 14:42
I thought it might be useful to show a simple example where the Model communicates with the Viewer and/or the Controller since "MVC dictates that all user data be stored in the Model layer" [ref].
Spoiler
My honest opinion as far as MVC and AHK go is that the script body is the control class. Model classes aren't needed in AHK. As soon as you added SetTimer ObjBindMethod(this, "Count_Monitor") and Count_Monitor() the class is no longer a model and becomes a controller. Models only hold information they do not do any work. The view class should also not be doing any work. Using a View/ViewModel approach is easier IMO
Spoiler

Post Reply

Return to “Tips and Tricks”