Skip to content

Commit 5f5a88f

Browse files
authored
Merge pull request #60 from urbytes21/dev_branch_3
Add an MVVM example
2 parents 870e9f3 + c41c80f commit 5f5a88f

16 files changed

Lines changed: 332 additions & 12 deletions

src/ap/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ target_sources(ap
1818
target_link_libraries(ap PRIVATE gtk4-settings)
1919

2020
add_subdirectory(mvc)
21-
# add_subdirectory(mvvm)
21+
add_subdirectory(mvvm)

src/ap/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ View → Controller → Model
4545
### 3. GTK4
4646
- [Refer](https://docs.gtk.org/gtk4/getting_started.html)
4747

48-
### 4. Examples
49-
### 4.1. simple_ap
50-
- Cos:
51-
- Quick, Simple
52-
- Pos:
53-
- Dependency: e.g. what happen when we delete Gtk::Label m_labelMonitorA;
54-
- Scalability:
55-
- Reusability:
48+
### 4. Trade-offs: MVC vs MVVM
5649

57-
### 4.2. mvc_ap
50+
| Aspect | MVC | MVVM |
51+
|---------------------|---------------------------------------------------------------------|----------------------------------------------------------------------|
52+
| **Complexity** | Lower — **Controller** is a thin pass-through | Slightly higher — **ViewModel** adds an extra layer |
53+
| **Coupling** | Views know both **Controller** and **Model** (e.g., for initial data) | **Views** know only the **ViewModel** |
54+
| **Testability** | Controller is testable, but **Views** are still tied to **Model** for reads | **ViewModel** is fully testable without GTK; Views are pure UI |
55+
| **Scalability** | Adding fields requires updating **Model**, **Controller**, and all **Views** | Adding fields requires updating **Model** and **ViewModel**; Views update bindings only |
56+
| **Observer wiring** | Manual — Container wires each **View** to the **Model** | Self-contained — **Views** register via **ViewModel**; container stays clean|
57+
| **UI logic leakage**| Risk - Views may call `model_->getData()` directly | Eliminated - Views use `viewModel_->getCurrentText()` only |

src/ap/mvc/IObserver.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <string>
34
class IObserver {
45
public:
56
virtual ~IObserver() = default;

src/ap/mvc/model/SharedData.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ void SharedData::setData(const std::string& data) {
99
}
1010

1111
void SharedData::notifyObservers() {
12-
for (auto o : observers_) {
12+
for (auto* o : observers_) {
1313
o->onDataChanged(this->data_);
1414
}
1515
}

src/ap/mvc/mvc_ap.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "view/DisplayWidget.h"
66
#include "view/EditorWidget.h"
77

8+
namespace mvc {
89
class ContainerWindow : public Gtk::Window {
910
public:
1011
ContainerWindow();
@@ -67,8 +68,9 @@ ContainerWindow::ContainerWindow()
6768
mainLayout_.append(*editorView_); // Add bottom row
6869
set_child(mainLayout_);
6970
}
71+
} // namespace mvc
7072

7173
int main(int argc, char* argv[]) {
7274
auto app = Gtk::Application::create("org.gtkmm.example.singlemvc");
73-
return app->make_window_and_run<ContainerWindow>(argc, argv);
75+
return app->make_window_and_run<mvc::ContainerWindow>(argc, argv);
7476
}

src/ap/mvvm/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_executable(mvvm_ap)
2+
3+
target_sources(mvvm_ap
4+
PRIVATE
5+
mvvm_ap.cpp
6+
model/SharedData.cpp
7+
viewmodel/SharedDataVM.cpp
8+
view/EditorWidget.cpp
9+
view/DisplayWidget.cpp
10+
)
11+
12+
target_link_libraries(mvvm_ap PRIVATE gtk4-settings)

src/ap/mvvm/IObserver.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#pragma once
2+
3+
#include <string>
4+
class IObserver {
5+
public:
6+
virtual ~IObserver() = default;
7+
virtual void onDataChanged(const std::string& newData) = 0;
8+
};

src/ap/mvvm/model/SharedData.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include "SharedData.h"
2+
3+
mvvm::SharedData::SharedData() : data_{"Initial Data"} {}
4+
5+
void mvvm::SharedData::setData(const std::string& data) {
6+
this->data_ = data;
7+
notifyObservers();
8+
}
9+
10+
void mvvm::SharedData::notifyObservers() {
11+
for (auto* o : observers_) {
12+
o->onDataChanged(this->data_);
13+
}
14+
}
15+
void mvvm::SharedData::addObserver(IObserver* obs) {
16+
if (obs != nullptr)
17+
observers_.push_back(obs);
18+
}
19+
20+
std::string mvvm::SharedData::getData() const {
21+
return data_;
22+
}

src/ap/mvvm/model/SharedData.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
#include <string>
3+
#include <vector>
4+
#include "../IObserver.h"
5+
6+
namespace mvvm {
7+
class SharedData {
8+
public:
9+
SharedData();
10+
11+
void setData(const std::string& data);
12+
std::string getData() const;
13+
14+
void addObserver(IObserver* obs);
15+
16+
private:
17+
void notifyObservers();
18+
19+
std::string data_;
20+
std::vector<IObserver*> observers_;
21+
};
22+
} // namespace mvvm

src/ap/mvvm/mvvm_ap.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <gtkmm.h>
2+
#include <memory>
3+
#include "model/SharedData.h"
4+
#include "view/DisplayWidget.h"
5+
#include "view/EditorWidget.h"
6+
#include "viewmodel/SharedDataVM.h"
7+
8+
namespace mvvm {
9+
/**
10+
* @brief ContainerWindow wires up the MVVM triad:
11+
* Key differences from MVC's ContainerWindow:
12+
* 1. No Controller is created.
13+
* 2. No manual addObserver() calls, each View self-registers with the ViewModel during construction.
14+
* 3. Views receive a shared_ptr<SharedDataVM>, never a Model pointer.
15+
*/
16+
class ContainerWindow : public Gtk::Window {
17+
public:
18+
ContainerWindow();
19+
20+
private:
21+
// Main Layout
22+
Gtk::Box mainLayout_;
23+
Gtk::Box topRowLayout_; // Horizontal arrangement (2 Displays side-by-side)
24+
25+
// Model & ViewModel are shared
26+
// View hold a shared_ptr to the ViewModel
27+
std::shared_ptr<SharedData> dataModel_;
28+
std::shared_ptr<SharedDataVM> viewModel_;
29+
30+
// Views
31+
std::unique_ptr<EditorWidget> editorView_;
32+
std::unique_ptr<DisplayWidget> displayViewLeft_;
33+
std::unique_ptr<DisplayWidget> displayViewRight_;
34+
};
35+
36+
ContainerWindow::ContainerWindow()
37+
: mainLayout_(Gtk::Orientation::VERTICAL),
38+
topRowLayout_(Gtk::Orientation::HORIZONTAL) {
39+
set_title("MVVM Integrated Demo");
40+
set_default_size(600, 400);
41+
42+
// Step 1 – construct the Model.
43+
dataModel_ = std::make_shared<SharedData>();
44+
45+
// Step 2 – construct the ViewModel; it subscribes to the Model internally.
46+
viewModel_ = std::make_shared<SharedDataVM>(dataModel_);
47+
48+
// Step 3 – construct Views, passing only the ViewModel.
49+
editorView_ = std::make_unique<EditorWidget>(viewModel_);
50+
displayViewLeft_ = std::make_unique<DisplayWidget>("ZONE 2: MONITOR A (Blue)",
51+
"blue", viewModel_);
52+
displayViewRight_ = std::make_unique<DisplayWidget>("ZONE 3: MONITOR B (Red)",
53+
"red", viewModel_);
54+
55+
// Layout, unchanged from MVC
56+
displayViewLeft_->set_hexpand(true);
57+
displayViewRight_->set_hexpand(true);
58+
topRowLayout_.append(*displayViewLeft_);
59+
topRowLayout_.append(*displayViewRight_);
60+
61+
editorView_->set_vexpand(false);
62+
mainLayout_.append(topRowLayout_);
63+
mainLayout_.append(*editorView_);
64+
set_child(mainLayout_);
65+
}
66+
} // namespace mvvm
67+
68+
int main(int argc, char* argv[]) {
69+
auto app = Gtk::Application::create("org.gtkmm.example.singlemvvm");
70+
return app->make_window_and_run<mvvm::ContainerWindow>(argc, argv);
71+
}

0 commit comments

Comments
 (0)