Skip to content

Instantly share code, notes, and snippets.

@arthurafarias
Last active July 8, 2023 11:43
Show Gist options
  • Save arthurafarias/8fc3e213ace392713a1468de9c66b3d9 to your computer and use it in GitHub Desktop.
Save arthurafarias/8fc3e213ace392713a1468de9c66b3d9 to your computer and use it in GitHub Desktop.
A Simple Model View Architecture in C++

Intro

This is a simple View, ViewModel, Model architecture inspired on .NET

It was done just for study purpose. I just don't want to forget it in the future.

I just took some notes and tried to implement something similar in C++ and STL.

Obviously, .NET relies on a Event Loops and the event dispatching occours assynchronously.

It's not ideal to use a DataContext that relies on references, but on smart pointers and reference counters.

The next version of this Gist is intended to accomplish with a design pattern that holds the lifecicle of every MVC namespace elements.

THe cool part of this concept is:

  1. When a property is changed in the view model and the view is listening to a property change of the affected propery, it is going the callback is going to be triggered it is an interesting approach (syntatically). But refinements are necessary.
#include <list>
#include <functional>
#include <iostream>
#include <string>
namespace Core
{
class Object {};
namespace Collections
{
}
namespace Functional
{
template<typename ...Args>
class ISlot : public Object
{
public:
virtual void operator()(Args&&... args) = 0;
};
template<typename ...Args>
class Slot : public ISlot<Args...>
{
std::function<void(Args...)> Function;
public:
Slot(std::function<void(Args...)>&& function)
{
Function = std::move(function);
}
void operator()(Args&&... args)
{
Function(std::forward<Args>(std::move(args))...);
}
};
}
namespace Events
{
template<typename ...Args>
class Signal: public Object
{
public:
typedef Functional::Slot<Args...> SlotType;
Signal() = default;
void Connect(SlotType&& slot)
{
Slots.push_back(slot);
}
void Emit(Args&&... data)
{
for (auto & slot : Slots)
{
slot(std::forward<Args>(data)...);
}
}
private:
std::list<SlotType> Slots;
};
}
namespace MVC
{
class INotifyPropertyChanged
{
public:
typedef Events::Signal<std::string> SignalType;
SignalType PropertyChanged;
};
class ModelBase : public Object
{
};
class ModelViewBase : public Object, public INotifyPropertyChanged
{
public:
};
class Component : public Object
{
};
class ViewBase : public Component
{
public:
ModelViewBase& DataContext;
ViewBase(decltype(DataContext)& dataContext) : DataContext(dataContext) {}
virtual void InitializeComponent() = 0;
};
class ListViewBase : public ViewBase
{
};
class ListItemViewBase : public ViewBase
{
};
class ListItemView : public ListItemViewBase
{
protected:
ModelViewBase& DataContext;
};
}
namespace App
{
class TargetModel : MVC::ModelBase
{
public:
double Latitude;
double Longitude;
double Elevation;
};
class TargetModelView : private TargetModel, public MVC::ModelViewBase
{
public:
#define CREATE_PROPERTY_WRAPPER(PropertyType, PropertyName) \
void Set##PropertyName(PropertyType _##PropertyName) \
{ \
this->PropertyName = _##PropertyName; \
PropertyChanged.Emit(#PropertyName); \
} \
\
double Get##PropertyName() \
{ \
return PropertyName; \
}
CREATE_PROPERTY_WRAPPER(double, Latitude)
CREATE_PROPERTY_WRAPPER(double, Longitude)
CREATE_PROPERTY_WRAPPER(double, Elevation)
};
class TargetView : public MVC::ViewBase, public MVC::ModelViewBase
{
decltype(TargetModelView::PropertyChanged)::SlotType OnDataContextPropertyChanged;
public:
TargetView() : MVC::ViewBase((MVC::ModelViewBase&)*this), OnDataContextPropertyChanged([&](std::string s){PrintAll();})
{
}
TargetView(TargetModelView& dataContext) : MVC::ViewBase(dataContext), OnDataContextPropertyChanged([&](std::string s){PrintAll();})
{
}
void InitializeComponent()
{
TargetModelView& modelView = static_cast<TargetModelView&>(DataContext);
OnDataContextPropertyChanged = decltype(OnDataContextPropertyChanged)([&](std::string property){ PrintAll(); });
modelView.PropertyChanged.Connect(std::move(OnDataContextPropertyChanged));
}
void PrintAll()
{
TargetModelView& modelView = static_cast<TargetModelView&>(DataContext);
std::cout << "Latitude " << modelView.GetLatitude() << std::endl;
std::cout << "Longitude " << modelView.GetLongitude() << std::endl;
std::cout << "Elevation " << modelView.GetElevation() << std::endl;
}
};
}
}
int main()
{
Core::App::TargetModelView targetModelView;
Core::App::TargetView targetView(targetModelView);
targetView.InitializeComponent();
((Core::App::TargetModelView&) targetView.DataContext).SetLatitude(1.0);
((Core::App::TargetModelView&) targetView.DataContext).SetLongitude(2.0);
((Core::App::TargetModelView&) targetView.DataContext).SetElevation(3.0);
targetModelView.SetLatitude(2.0);
}
@arthurafarias
Copy link
Author

arthurafarias commented Jul 7, 2023

The result of this code is (by example)

Latitude 1
Longitude 6.95335e-310
Elevation 6.95335e-310

Latitude 1
Longitude 2
Elevation 6.95335e-310

Latitude 1
Longitude 2
Elevation 3

Latitude 2
Longitude 2
Elevation 3

For every property changed done in the ModelView,
the View Component, who is listening to all property
chages, print all information about the DataContext.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment