Component model
Home
Essentials
Component model
Components and controls
In Minimal library, we distinguish between components and controls. Every M-control (control from Minimal library) is also a component (M-Component), but not every component is necessarily a control. Controls can be placed directly on the form because they inherit from System.Windows.Forms.UserControl class. Components, on the other hand, can extend any class and may not necessarily be placeable on the form on their own. When not extending UserControl class, components usually contain control internally so they act like a wrapper. Good "non-control" component example is MContextMenu, which can’t be placed directly on the form, but after initialization and Show() call will utilize inner list-box control which will then present all menu items to the user. All components in Minimal library always implements IMComponent interface.
Next sections might be hard to understand if you are completely new to the Minimal library. We suggest first learn the basics and then come back!
Writing custom M-Component
In order to create a new M-Component you need to implement IMComponent interface. This interface will ensure, that your class implements MComponent property which handles life-cycle of your component.
class CustomComponent : IMComponent
{
public MComponent Component { get; set; }
}
After implementing an interface we have to tell a library that we have a new component to handle. This step may vary depending on if you are creating a M-Component or M-Control. In our case we can simply call ComponentManager.RegisterComponent() method with reference on our class as parameter in the constructor:
class CustomComponent : IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Constructor
public CustomComponent()
{
// Registers our new component
ComponentManager.RegisterComponent(this);
}
}
RegisterComponent() method will create a new instance of the MComponent and save it to the MComponent property in our newly created class. After registering is MComponent property hearth of our custom component. Using this object you can for example listen to the ThemeChanged event and get notified, when form’s theme is changed or you can hook up the update method. How to work with component object is described more below.
Writing custom M-Control
As written above, M-Controls always extends child class of the UserControl or the UserControl class directly. This class has all the basic functionality for a graphical control that will be used on a Windows Form. All of the built-in .NET controls inherit from this same base class. Faster approach of creating a .NET custom control is using Visual Studio build-in commands. You can right click your project in Solution Explorer, choose Add and then UserControl. You should end up with code structure similar to this:
public partial class CustomControl : UserControl
{
public CustomControl()
{
InitializeComponent();
}
}
Now we can implement IMComponent interface and give our standard .NET control some super powers:
public partial class CustomControl : UserControl, IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Constructor
public CustomControl()
{
// Register a new component
ComponentManager.RegisterComponent(this);
// Initialization
InitializeComponent();
}
}
Working with component object
In previous sections we have created a basic M-Component and M-Control. In this section we will cover how to work with MComponent object and how to take advantage of all the benefits of the library.
Life-cycle hooks
Each component instance goes through a series of initialization steps when it’s created. Along the way, it also invokes some events. Methods which responds to those events are called life-cycle hooks, giving users the opportunity to add their own code at specific stages.
If you are working inside a class derived from the UserControl, you need to register your life-cycle hooks in the UserControl.HandleCreated event. See examples below for more information.
Component update event
Update method can be created as an answer to the MComponent.ComponentUpdate event. This event is triggered automatically by the library every 1 milisecond. If MComponent.Outdated property is false, then event is not invoked. You can use this behaviour to save some performance when control is up to date and you don’t need to call an update logic.
public partial class CustomControl : UserControl, IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Constructor
public CustomControl()
{
// Register a new component
ComponentManager.RegisterComponent(this);
// Initialization
InitializeComponent();
}
// Occurs when a handle is created for the control. Handles
// event hooking.
protected override void OnHandleCreated(EventArgs e)
{
// Base call
base.OnHandleCreated(e);
// Hook up an update method. MUST BE CALLED INSIDE HANDLE
// CREATED EVENT!
Component.ComponentUpdate += OnComponentUpdate;
}
// Update method. Called every 1 ms.
private void OnComponentUpdate(object sender, EventArgs e)
{
}
}
Notice, that OnComponentUpdate method is assigned to the event handler inside the HandleCrated event. This is absolutely crucial for the functionality of the component. If you are creating a custom M-Component, you of course don’t have access to the HandleCrated event. In those cases you can assign OnComponentUpdate method in the constructor:
class CustomComponent : IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Constructor
public CustomComponent()
{
// Registers our new component
ComponentManager.RegisterComponent(this);
// Hook up an update method. Can be called inside
// constructor because we don't inherit from
// UserControl class.
Component.ComponentUpdate += OnComponentUpdate;
}
// Update method. Called every 1 ms.
private void OnComponentUpdate(object sender, EventArgs e)
{
}
}
With this knowledge we can now create a simple counter control. Using update method we will increment our count variable and then draw that variable by overriding Paint method. Notice the Invalidate() method which is called at the end of the OnComponentUpdate method. This will ensure that our control gets redrawed every frame after our update logic.
public partial class CustomControl : UserControl, IMComponent
{
// Component property
public MComponent Component { get; set; }
// Count number
int count = 0;
// Constructor control
public CustomControl()
{
// Register a new component
ComponentManager.RegisterComponent(this);
// Initialization
InitializeComponent();
}
// Occurs when a handle is created for the control. Handles
// event hooking.
protected override void OnHandleCreated(EventArgs e)
{
// Base call
base.OnHandleCreated(e);
// Hook up an update method
Component.ComponentUpdate += OnComponentUpdate;
}
// Update method
private void OnComponentUpdate(object sender, EventArgs e)
{
// Increment count number
count++;
// Redraw our control (calls OnPaint method)
Invalidate();
}
// Draw method
protected override void OnPaint(PaintEventArgs e)
{
// Base call
base.OnPaint(e);
// Set background color
e.Graphics.Clear(Color.LightGray);
// Draw count number
e.Graphics.DrawString(count.ToString(), Font, Brushes.Black, new Point());
}
}
public partial class CustomControl : UserControl, IMComponent
{
// Component property
public MComponent Component { get; set; }
// Count number
int count = 0;
// Constructor control
public CustomControl()
{
// Register a new component
ComponentManager.RegisterComponent(this);
// Initialization
InitializeComponent();
}
// Occurs when a handle is created for the control. Handles
// event hooking.
protected override void OnHandleCreated(EventArgs e)
{
// Base call
base.OnHandleCreated(e);
// Hook up an update method
Component.ComponentUpdate += OnComponentUpdate;
}
// Update method
private void OnComponentUpdate(object sender, EventArgs e)
{
// Increment count number
count++;
// Redraw our control (calls OnPaint method)
Invalidate();
}
// Draw method
protected override void OnPaint(PaintEventArgs e)
{
// Base call
base.OnPaint(e);
// Set background color
e.Graphics.Clear(Color.LightGray);
// Draw count number
e.Graphics.DrawString(count.ToString(), Font, Brushes.Black, new Point());
}
// Mouse entered
protected override void OnMouseEnter(EventArgs e)
{
// Base call
base.OnMouseEnter(e);
// Block updates
Component.Outdated = false;
}
// Mouse leaved
protected override void OnMouseLeave(EventArgs e)
{
// Base call
base.OnMouseLeave(e);
// Allow updates
Component.Outdated = true;
}
}
Using Outdated property we can stop counting after user hovers over the control and then continue again after mouse leave the control. See second example above. Preview of our newly created control can be seen below:
Theme changed event
The ThemeChanged life-cycle event is invoked when theme of the parent MForm is changed. Using the ThemeChangedEventArgs.Theme you can get a theme which was set to the parent MForm. Please note, that you do not have to set ThemeChangedEventArgs.Theme variable to the Component.SourceTheme manually. The library will do it automatically for you.
Input type changed event
This life-cycle event is called when M.TouchEnabled property is changed. Using this event your components can react to input type changes and for example they can make their text bigger for better touch experience. Using InputTypeChangedEventArgs.TouchEnabled you can get bool value depending on if touch input is on or off.
Source theme property
Source theme property contains instance of Theme class which is later used for final component rendering. This property is automatically changed based on component’s parent form, application wide theme or on CustomTheme property.
// Render (draw) method
protected override void OnPaint(PaintEventArgs e)
{
// Always use Component.SourceTheme property for
// component rendering!
e.Graphics.Clear(Component.SourceTheme.CONTROL_FILL.Normal);
}
Custom theme property
When CustomTheme property is set, M.ApplicationWideTheme and theme of parent MForm are ignored. Instead, CustomTheme instance is passed to the SourceTheme property and component gets finally rendered with custom theme.
// Custom theme property
public Theme CustomTheme
{
// Always return Component.CustomTheme !
get { return Component.CustomTheme; }
set
{
// Always change Component.CustomTheme !
Component.CustomTheme = value;
}
}
Parent form property
In case of M-Controls it is really simple to get parent form of the control. In windows forms we can use FindForm() method which will return an instance of parent form. On the other hand, when we are dealing with M-Components we can not access this handy method. That is why every component instance comes with ParentForm property which tells us to what form a component belongs to. In case of M-Controls will ParentForm property return a same form instance as FindForm() method.
class CustomComponent : IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Constructor
public CustomComponent(Form parent)
{
// Registers our new component
ComponentManager.RegisterComponent(this);
// Update parent form
Component.ParentForm = parent;
}
}
Accent property
Accent is main visible color of the component. Components should not implement an accent color property on their own but instead use MComponent.Accent property.
// Accent property
public Color Accent
{
// Always return Component.Accent !
get { return Component.Accent; }
set
{
if (value != Component.Accent)
{
// Always change Component.Accent !
Component.Accent = value;
// Redraw component
Invalidate();
}
}
}
Conclusion
With all that has been said above, the basic template for the M-component and M-control might look like this:
class CustomComponent : IMComponent
{
// MComponent property
public MComponent Component { get; set; }
// Accent property
public Color Accent
{
// Always return Component.Accent !
get { return Component.Accent; }
set
{
if (value != Component.Accent)
{
// Always change Component.Accent !
Component.Accent = value;
// Redraw component
Invalidate();
}
}
}
// Custom theme property
public Theme CustomTheme
{
// Always return Component.CustomTheme !
get { return Component.CustomTheme; }
set
{
// Always change Component.CustomTheme !
Component.CustomTheme = value;
}
}
// Constructor
public CustomComponent(Form parent)
{
// Registers our new component
ComponentManager.RegisterComponent(this);
// Update parent form
Component.ParentForm = parent;
}
}
public partial class CustomControl : UserControl, IMComponent
{
// Component property
public MComponent Component { get; set; }
// Accent property
public Color Accent
{
// Always return Component.Accent !
get { return Component.Accent; }
set
{
if (value != Component.Accent)
{
// Always change Component.Accent !
Component.Accent = value;
// Redraw component
Invalidate();
}
}
}
// Custom theme property
public Theme CustomTheme
{
// Always return Component.CustomTheme !
get { return Component.CustomTheme; }
set
{
// Always change Component.CustomTheme !
Component.CustomTheme = value;
}
}
// Constructor control
public CustomControl()
{
// Register a new component
ComponentManager.RegisterComponent(this);
// Initialization
InitializeComponent();
}
// Occurs when a handle is created for the control. Handles
// event hooking.
protected override void OnHandleCreated(EventArgs e)
{
// Base call
base.OnHandleCreated(e);
// Hook up an update method
Component.ComponentUpdate += OnComponentUpdate;
}
// Update method
private void OnComponentUpdate(object sender, EventArgs e)
{
// Redraw our control (calls OnPaint method)
Invalidate();
}
// Draw method
protected override void OnPaint(PaintEventArgs e)
{
// Base call
base.OnPaint(e);
// Draw your control here...
}
}