|
|
|||||||
| Home | Register | Downloads | FAQ | Members List | Calendar | Arcade | Mark Forums Read |
» Less advertising throughout
» Post and participate in discussions
» Network with other forum members
» Free private messaging
|
|
Thread Tools | Display Modes |
|
|
#1 | ||
|
Crazy GFX coder
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Join Date: Nov 2002
Location: Dominican Republic/Austria
Posts: 8,106
|
MVVM coding pattern and global Exception handling Tutorial
After chatting with some developers i got asked to post a tutorial here in order to explain a bit how MVVM actally works and why it works. MVVM can be used in C#, WPF, C++(Qt) and Java(eFACE). Since am short in time this days i'll try to write some posts later explaining more about t. MVVM is basically the "art" of separating your code from the UI so that your code have no idea about your UI yet can be used on it anytime. Now... what's so great about separating the logic and the UI? quite simple... there are times when things in the UI changed and that is the time a lot of coders spend an incredible amount of time trying to fix broken events or missing UI objects in the code behind after a big facial change. By using proper MVVM coding pattern you totally free your code and because of that you are able to change anything in the UI or in your logic without being affraid of breaking something in your code or just getting insane amounts of errors/exceptions just because something is missed. Here a small wikipedia explanation: Quote:
In this tutorial am going to handle 2 big points that may help others to improve there way of coding as also keep there code clean and those 2 points are: - MVVM(Model View ViewModel) - Global Exception handling Part I - MVVM(Model View ViewModel): The simpliest way to explain how it works is imagine a machine that expects information of any kind by expecting a determined set of properties. if you feed that machine with information and any part of that information matches to the ones expected then only those will work and the rest will just not be used until the administrator decide to add those missed definitions back(hope that is easy enough). To explain it better am going to use WPF and C# and am going to create a new project(In this case i will use @ES MVVM base project): ![]() As you can notice i've created several sub-projects which is something very common but.... if you look closely you will notice that i create a project for the UI only and another one for the UI logic and both are in separated projects. Now here is the trick... I create an application which serves as a place holder and load the necessary layers on it on demand: Main application XAML: Code:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GraFX.Main"
x:Name="GraFXMain"
Title="@ES - GraFX 'Gray Moon'"
MinWidth="706" MinHeight="510" Icon="/GraFX;component/mario.ico">
<Grid x:Name="LayoutRoot">
</Grid>
</Window>
Code:
using AES.UI.Main;
namespace GraFX
{
/// <summary>
/// Main Window
/// Here we load plugins, layers, content and skins
/// </summary>
public partial class Main
{
/// <summary>
/// Base constructor
/// </summary>
public Main()
{
InitializeComponent();
Content = new UIMain();
}
}
}
Code:
<UserControl x:Class="AES.UI.Main.UIMainContent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignWidth="706" d:DesignHeight="510">
<Grid>
<Viewbox Margin="20,39,0,312" HorizontalAlignment="Left">
<Grid Height="165" Width="686" HorizontalAlignment="Left">
<Label x:Name="UILabelTime" Margin="0,0,0,55" Content="{Binding CurrentTime}" Foreground="WhiteSmoke" FontSize="76" HorizontalAlignment="Left" />
<Label x:Name="UILabelTDate" VerticalAlignment="Bottom" Height="80" Content="{Binding CurrentDate}" Foreground="WhiteSmoke" FontSize="35" HorizontalAlignment="Left" />
</Grid>
</Viewbox>
</Grid>
</UserControl>
Code:
using AES.ViewModels.ViewModels.Main;
namespace AES.UI.Main
{
/// <summary>
/// Interaction logic for UIMainContent.xaml
/// </summary>
public partial class UIMainContent
{
public UIMainContent()
{
InitializeComponent();
DataContext = new UIMainContentViewModel();
}
}
}
Now lets have a close look to our MainViewModel class: Code:
using System;
using System.Globalization;
using System.ComponentModel;
using System.Windows.Threading;
namespace AES.ViewModels.ViewModels.Main
{
public class UIMainContentViewModel : INotifyPropertyChanged
{
/// <summary>
/// this is the timer we're going to use to display the current time
/// </summary>
private readonly DispatcherTimer timer;
/// <summary>
/// Get or Set current time variable
/// </summary>
private string currentTime;
/// <summary>
/// Get or Set current date variable
/// </summary>
private string currentDate;
/// <summary>
/// Current time binding property
/// </summary>
public string CurrentTime
{
get { return currentTime; }
set { currentTime = value; InvokePropertyChanged("CurrentTime"); }
}
/// <summary>
/// Current date binding property
/// </summary>
public string CurrentDate
{
get { return currentDate; }
set { currentDate = value; InvokePropertyChanged("CurrentDate"); }
}
/// <summary>
/// Base constructor
/// </summary>
public UIMainContentViewModel()
{
//initialize timers
timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1000) };
timer.Tick += TimerTick;
timer.Start();
}
/// <summary>
/// Timer tick responsible to display the time and date on the main layer
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void TimerTick(object sender, EventArgs e)
{
SetTimeAndDate();
}
/// <summary>
/// Set current time and date values
/// </summary>
private void SetTimeAndDate()
{
CurrentTime = DateTime.Now.ToShortTimeString();
CurrentDate = string.Format("{0} {1} {2}", DateTime.Now.Day, CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(DateTime.Now.Month), DateTime.Now.Year);
}
/// <summary>
/// Event trigger when a property has changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Invoke property name using our trigger
/// </summary>
/// <param name="propertyName">Name of the property</param>
private void InvokePropertyChanged(string propertyName)
{
PropertyChangedEventHandler changed;
switch (propertyName)
{
default:
changed = PropertyChanged;
break;
}
if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
}
}
}
With that in mind you now can see how it actually works. Basically you change your properties or collections and they are reflected back to the UI by using bindings. While i have to admit that it may sound strange i can garantee you that once you get the idea you will fully understand how it works and why it works Down sides: Not every stone that shines may be gold.... that's a fact we all know and just like any other coding patterns there are pros and contras. One of the tipical problems that may rise are when you really need access to a actual object of your UI but since you separated both how in hell are you going to achieve that??? MVVM doesn't prohibits you from using codebehind at all but just avoid it as much as you can... now what to do??? one way to achieve your goal is by using events callbacks from the ViewModel. Since those are event callbacks they an easily be used in your codebehind and if you don't it won't break you code either. To achieve that goal you start by declaring a event callback: Code:
/// <summary>
/// Triggers on settings event
/// </summary>
public event PropertyChangedEventHandler OnSettings;
Here the invoke handling: Code:
/// <summary>
/// Invoke property name using our trigger
/// </summary>
/// <param name="propertyName">Name of the property</param>
private void InvokePropertyChanged(string propertyName)
{
PropertyChangedEventHandler changed;
switch (propertyName)
{
case "OnFullScreen":
changed = OnFullScreen;
break;
case "OnSettings":
changed = OnSettings;
break;
default:
changed = PropertyChanged;
break;
}
if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
}
Code:
/// <summary>
/// Base constructor
/// </summary>
public UIMain()
{
InitializeComponent();
UILMainViewModel main = new UILMainViewModel();
main.OnSettings += main_OnSettings;
DataContext = main;
}
void main_OnSettings(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//Event callback from my ViewModel
}
Part II - Global Exception handling: One of the biggest problems is to handle exceptions in a good way. Ever seen a project full of try-catches? we could say that one should write the code in a good way so that such try-catches in the code aren't needed all the time... While that may be true at a certain level there are areas were something unexpected may happen and your app will sudendly die. Now what to do??? should a coder write try-catches on every single method written???? obviously that would be ugly and silly at the sametime. also there are times when the coder wants to know what was wrong but don't want the application to die but just to stay in its previous state which is the one before the code that caused the exception was executed. Now how to achieve that???? recently at work we got the challenge to do exactly that and after a while thinking in a solution something came to my mind that does exactly that(at least in C# and WPF). Every WPF application can have an app.config which is just a XML settings file and i remember that you can set the runtime policy there and one of them is nothing but the "legacyUnhandledExceptionPolicy" which if enabled set a dispatcher to the exception in order to catch it even if it wasn't handled. Now all we have to do is catch the exception and Voila!!! to do that we use our App.XAML.cs and write the following: Code:
using System;
using System.IO;
using System.Windows;
namespace GraFX
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
public App()
{
Dispatcher.UnhandledException += OnDispatcherUnhandledException;
}
void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
var errorMessage = string.Format("An exception occurred: {0}", e.Exception.Message);
MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
//Write log containing our exception information
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "\\log.txt", e.Exception.StackTrace);
e.Handled = true;
}
}
}
A very important fact is that this is not a "on error resume next" silliness but what it does is try to execute a code and if it fails it will execute the dispatcher which will send the exception object to the app.XAML.cs for handling. What is more important... the app will return to the previous state as if the code was never executed(not totally true but you get the idea). The idea is to determine in our global exception handler if the exception that was thrown was a critical one and in that case ask the user to re-start if necessary. However during my tests i can say that a re-start is not necessary at all. Here is an example: Lets say i try to convert "22A" to a integer. As every coder here know that will throw an exception and if not handled my app will suddendly die. Now lets see what happen to my app: ![]() As you can see here the debugger is showing me the exception and as a coder i usually know what that means and that's no other than the fact that my app is gonna die after i press F5 to continue.... since i added my code lets see what really happen this time: ![]() As you can see here the dispatcher catched the exception and the message was displayed and loged in my log.txt file. After clicking the OK button my app is still alive and kicking and at the same state as it was before the code that caused the exception was thrown ![]() Here the stacktrace from my log.txt: Quote:
however this is not the @ES - GraFX source code but just the start base of it.If you have any questions or suggestion do not hesitate to ask them. I may not respond immediatly since am very busy this days but i do check the forums frequently enough to answer your questions Happy coding!
__________________
![]() Current development tools: Visual C++.net, Visual C#.net Visual VB.net, Visual Webdeveloper.net Bloodshed Dev C++, Borland C++ Visual Basic 6 Last edited by @ruantec; February 23rd, 2012 at 07:05.. |
||
|
|
|
| Advertisement | [Remove Advertisement] | ||
|
| Thread Tools | |
| Display Modes | |
|
|