Archive for the ‘Presentation’ Category

INotifyPropertyChanged with lambdas

Tuesday, February 16th, 2010

There are several ways of implementing INotifyPropertyChanged in WPF applications.

One that I particularly like is by using PostSharp to implement INotifyPropertyChanged.

If you are not comfortable with PostSharp you can at least get rid of those nasty strings in standard implementation:

public class Model : ViewModeBase
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            _status = value;
            OnPropertyChanged("Status");
        }
    }
};

Labda expressions look nicer and are easier to refactor:

public class MyModel : ViewModeBase
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            _status = value;
            OnPropertyChanged(() => Status);
        }
    }
};

First thing we need to do is to create base class for all our ViewModels:

public class ViewModelBase
{
    public event PropertyChangedEventHandler PropertyChanged
        = delegate { };

    protected void OnPropertyChanged(
        Expression<Func<object>> expression)
    {
        string propertyName = PropertyName.For(expression);
        this.PropertyChanged(
            this,
            new PropertyChangedEventArgs(propertyName));
    }
};

The implementation of PropertyName.For is very straightforward: How to get property name from lambda.

That’s it!
Nice, easy to refactor, compilaile-time checked INotifyPropertyChanged implementation.

Background processing in WinForms

Wednesday, February 3rd, 2010

Many times developing windows applications, you’ll need to perform some operations in background.

The problem you’ll face sooner or later is that those operations need to inform User Interface (UI) about their progress and completion.

UI doesn’t like to be informed about anything from a different thread: you’ll get nasty “Cross-thread operation not valid” exception from WinForms controls, if you try.

Let’s take a look at the sample Presenter code:

public void Start()
{
        TaskStatus taskStatus = this._backupService.CreateTask();
        taskStatus.Completed += BackupFinished;
        this._backupService.Start(taskStatus);
}

TaskStatus contains single event Completed.
What’ll do is that we’ll subscribe to this event to display some information on the View:

public void BackupFinished(object sender, EventArgs e)
{
        // If the operation is done on different thread,
        // you'll get "Cross-thread operation not valid"
        // exception from WinForms controls here.
        this.View.ShowMessage("Finished!");
}

So, what are the options:

Lets examine the last concept as SynchronizationContext is not a well-know-class in the .NET world.
Generally speaking this class is useful for synchronizing calls from worker thread to UI thread.

It has a static Current property that gets the synchronization context for the current thread or null if there is no UI thread (e.g. in Console application)

This is the TaskStatus class that utilizes SynchronizationContext.Current if it is not null:

public class TaskStatus
{
    private readonly SynchronizationContext _context;

    public event EventHandler Completed = delegate { };

    public TaskStatus()
    {
        _context = SynchronizationContext.Current;
    }

    internal void OnCompleted()
    {
        Synchronize(x => this.Completed(this, EventArgs.Empty));
    }

    private void Synchronize(SendOrPostCallback callback)
    {
        if (_context != null)
            _context.Post(callback, null);
        else
            callback(null);
    }
};

Now lets see some tests.

First we’ll check if the event is executed:

[Test]
public void Completed_RaisesCompleted()
{
    using(SyncContextHelper.No())
    {
        bool wasFired = false;
        TaskStatus status = new TaskStatus();
        status.Completed += (sender, args) => { wasFired = true; };
        status.OnCompleted();
        Assert.IsTrue(wasFired);
    }
}

The following test shows that in WindowsForms application, although operation is executed on different thread, Completed event is routed back (using windows message queue) to the UI thread:

[Test]
public void Completed_WithSyncContext_IsExecutedOnSameThread()
{
    using (SyncContextHelper.WinForms())
    {
         int completedOnThread = -1;
         int thisThread = Thread.CurrentThread.GetHashCode();

         TaskStatus status = new TaskStatus();
         status.Completed += (sender, args) =>
             {
                 completedOnThread =
                    Thread.CurrentThread.GetHashCode();
             };

         Scenario.ExecuteOnSeparateThread(status.OnCompleted);

         // process messages send from background thread
         // (like Completed event)
         Application.DoEvents();

         Assert.AreEqual(thisThread, completedOnThread);
    }
}

When there is no SynchronizationContext (SynchronizationContext.Current == null) Completed event is executed on the different thread:

[Test]
public void Completed_InMultiThreadedScenario_IsExecuedOnDifferentThread()
{
    using(SyncContextHelper.No())
    {
        int completedOnThread = -1;
        int thisThread = Thread.CurrentThread.GetHashCode();

        TaskStatus status = new TaskStatus();
        status.Completed += (sender, args) =>
            {
                completedOnThread =
                    Thread.CurrentThread.GetHashCode();
            };

        Scenario.ExecuteOnSeparateThread(status.OnCompleted);

        Assert.AreNotEqual(thisThread, completedOnThread);
    }
}

Finally unit test helper classes:

public class SyncContextHelper : IDisposable
{
    private readonly SynchronizationContext _previous;

    private SyncContextHelper(SynchronizationContext context)
    {
        _previous = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(context);
    }

    public static SyncContextHelper WinForms()
    {
        return new SyncContextHelper(
            new WindowsFormsSynchronizationContext());
    }

    public static SyncContextHelper No()
    {
        return new SyncContextHelper(null);
    }

    public void Dispose()
    {
        SynchronizationContext.SetSynchronizationContext(_previous);
    }
};

Cross-thread operations with PostSharp

Tuesday, January 26th, 2010

When you try to inform User Interface (UI) about the background operation progress or completion, you can not do it from the background thread.

UI doesn’t like to be informed about anything from a different thread: you’ll get “System.InvalidOperationException: Cross-thread operation not valid: Control ‘xxx’ accessed from a thread other than the thread it was created on.” exception from WinForms control, if you try:

public void ShowStatus(ApplicationStatus status)
{
    this._lblServiceAddress.Text = "Connected to: "
        + status.WebServiceAddress;
    this._lblUserId.Text = "Working as: "
        + status.UserId;
}

The easiest sollution is to use BeginInvoke method on the control Control or Form:

public void ShowStatus(ApplicationStatus status)
{
    this.BeginInvoke((MethodInvoker)(() =>
        {
            this._lblServiceAddress.Text = "Connected to: "
                + status.WebServiceAddress;
            this._lblUserId.Text = "Working as: "
                + status.UserId;
        }));
}

Well, it’s fun to write this once, but if you have many operations done in background sooner or later you’d like to have something nicer. Like an attribute for example:

[ThreadAccessibleUI]
public void ShowStatus(ApplicationStatus status)
{
    this._lblServiceAddress.Text = "Connected to: " + status.WebServiceAddress;
    this._lblUserId.Text = "Working as: " + status.UserId;
}

Here’s the attribute implementation of such attribute using PostSharp 1.5:

/// <summary>
/// PostSharp attribute.
/// Use it to mark Control's methods that
/// are invoked from background thread.
/// </summary>
/// <remarks>
/// Be careful as BeginInvoke uses the message queue.
/// This means that the interface will be refreshed
/// when application has a chance to process its messages.
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
[Serializable] // required by PostSharp
public class ThreadAccessibleUIAttribute : OnMethodInvocationAspect
{
    public override void OnInvocation(
        MethodInvocationEventArgs eventArgs)
    {
        Control control = eventArgs.Instance as Control;
        if (control == null)
            throw new ApplicationException(
                "ThreadAccessibleUIAttribute" +
                "can be applied only to methods on Control class");

        // The form may be closed before
        // this method is called from another thread.
        if (control.Created == false)
            return;

        control.BeginInvoke((MethodInvoker)eventArgs.Proceed);
    }
};

M-V-VM talk

Tuesday, October 13th, 2009

thumbnail
Here are the slide-show and code from my recent M-V-VM talk on the Warsaw .NET group. I was talking about different flavors of MVVM, blendability, unit testing and separation of concerns. All in all, I think it was a good talk with great audience participation.

MVVM_Presentation
MyMVVMSample

Printing in WebBrowser control (custom header and footer)

Tuesday, September 29th, 2009

This is going to be a complicated one. Whole code/working sample is near the end of the article.

The Task:
Enable HTML printing using WebBrowser content but modify standard header and footer.

SampleApp

Unfortunately, when we print, nasty footer and header appear, and there is no way to get rid of them:
NastyFooter

ShowPrintDialog method comment explicitly says that header and footer can not be modified:
ShowPrintDialog

The Big Plan:
1. First we create IEPrinting.dll written in c++ and managed c++, it exposes one extremely simple helper class: PrintHelper:

// PrintHelper.h
#pragma once
using namespace System;
namespace IEPrinting
{
	public ref class PrintHelper
	{
		public:
			static void Print(IntPtr^ ptrIWebBrowser2, String^ header, String^ footer);
	};
}

2.
From now on things get complicated:

// This is the main DLL file.
#include "stdafx.h"
#include "PrintHelper.h"

namespace IEPrinting
{
#pragma unmanaged

int UnmanagedPrint(IWebBrowser2* webOC, BSTR header, BSTR footer)
{
	SAFEARRAYBOUND psabBounds[1];
	SAFEARRAY *psaHeadFoot;
	HRESULT hr = S_OK;

	// Variables needed to send IStream header to print operation.
	HGLOBAL hG = 0;
	IStream *pStream= NULL;
	IUnknown *pUnk = NULL;
	ULONG lWrote = 0;
	LPSTR sMem = NULL;

	// Initialize header and footer parameters to send to ExecWB().
	psabBounds[0].lLbound = 0;
	psabBounds[0].cElements = 3;
	psaHeadFoot = SafeArrayCreate(VT_VARIANT, 1, psabBounds);
	if (NULL == psaHeadFoot) {
		// Error handling goes here.
		goto cleanup;
	}
	VARIANT vHeadStr, vFootStr, vHeadTxtStream;
	long rgIndices;
	VariantInit(&vHeadStr);
	VariantInit(&vFootStr);
	VariantInit(&vHeadTxtStream);

	// Argument 1: Header
	vHeadStr.vt = VT_BSTR;
	vHeadStr.bstrVal = header;
	if (vHeadStr.bstrVal == NULL) {
		goto cleanup;
	}

	// Argument 2: Footer
	vFootStr.vt = VT_BSTR;
	vFootStr.bstrVal = footer;
	if (vFootStr.bstrVal == NULL) {
		goto cleanup;
	}

	// Argument 3: IStream containing header text. Outlook and Outlook
         // Express use this to print out the mail header.
	if ((sMem = (LPSTR)CoTaskMemAlloc(512)) == NULL) {
		goto cleanup;
	}
	// We must pass in a full HTML file here, otherwise this
         // becomes corrupted when we print.
	sprintf_s(sMem, 512, "<html><body><strong>Printed By:</strong>
	 Custom WebBrowser Host 1.0<p></body></html>\0");

	// Allocate an IStream for the LPSTR that we just created.
	hG = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, strlen(sMem));
	if (hG == NULL) {
		goto cleanup;
	}
	hr = CreateStreamOnHGlobal(hG, TRUE, &pStream);
	if (FAILED(hr)) {
		//ATLTRACE(_T("OnPrint::Failed to create stream from HGlobal: %lX\n"), hr);
		goto cleanup;
	}
	hr = pStream->Write(sMem, strlen(sMem), &lWrote);
	if (SUCCEEDED(hr)) {
	    // Set the stream back to its starting position.
		LARGE_INTEGER pos;
		pos.QuadPart = 0;
		pStream->Seek((LARGE_INTEGER)pos, STREAM_SEEK_SET, NULL);
		hr = pStream->QueryInterface(IID_IUnknown, reinterpret_cast<void **>(&pUnk));
		vHeadTxtStream.vt = VT_UNKNOWN;
		vHeadTxtStream.punkVal = pUnk;
	}

	rgIndices = 0;
	SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vHeadStr));
	rgIndices = 1;
	SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vFootStr));
	rgIndices = 2;
	SafeArrayPutElement(psaHeadFoot, &rgIndices, static_cast<void *>(&vHeadTxtStream));

	//NOTE: Currently, the SAFEARRAY variant must be passed by using
	// the VT_BYREF vartype when you call the ExecWeb method.
	VARIANT vArg;
	VariantInit(&vArg);
	vArg.vt = VT_ARRAY | VT_BYREF;
	vArg.parray = psaHeadFoot;

	//OLECMDEXECOPT_PROMPTUSER
	hr = webOC->ExecWB(OLECMDID_PRINT, OLECMDEXECOPT_PROMPTUSER, &vArg, NULL);

	if (FAILED(hr)) {
		//ATLTRACE(_T("DoPrint: Call to WebBrowser's ExecWB failed: %lX\n"), hr);
		goto cleanup;
	}
	return 1;
	//WebBrowser control will clean up the SAFEARRAY after printing.
	cleanup:
	VariantClear(&vHeadStr);
	VariantClear(&vFootStr);
	VariantClear(&vHeadTxtStream);
	if (psaHeadFoot) {
		SafeArrayDestroy(psaHeadFoot);
	}
	if (sMem) {
		CoTaskMemFree(sMem);
	}
	if (hG != NULL) {
		GlobalFree(hG);
	}
	if (pStream != NULL) {
		pStream->Release();
		pStream = NULL;
	}
	//bHandled = TRUE;
	return 0;
}
#pragma managed

	void PrintHelper::Print(IntPtr^ ptrIWebBrowser2, String^ header,  String^ footer)
	{
		IWebBrowser2* pBrowser = (IWebBrowser2 *)ptrIWebBrowser2->ToPointer();

		IDispatch *pDisp;
		pBrowser->get_Document(&pDisp);

		IHTMLDocument2 *pDoc;
		pDisp->QueryInterface<IHTMLDocument2>(&pDoc);

		IHTMLElement *body;
		pDoc->get_body(&body);

		BSTR p;
		body->get_innerHTML(&p); 

		IntPtr pHeader = Runtime::InteropServices::Marshal::StringToBSTR(header);
		IntPtr pFooter = Runtime::InteropServices::Marshal::StringToBSTR(footer);

		UnmanagedPrint(pBrowser, (BSTR)pHeader.ToPointer(), (BSTR)pFooter.ToPointer());
	}
}

3.
Now we need to generate C# version of the IWebBrowser2 COM interface:
It can be generated from idl -> tlb -> dll and the referenced from the WindowsForms app.

midl ExDisp.Idl /tlb ExDisp.tlb
pause
tlbimp ExDisp.tlb /out:ExDisp.dll
pause

4.
Then we create WindowsForms project, and create custom control inheriting
from WebBrowser control.
We need this to access IWebBrowser2 interface (defined in ExDisp.dll):

using ExDisp;
using WebBrowser = System.Windows.Forms.WebBrowser;

namespace WindowsFormsApplication1.MyBrowser
{
    public class MyWebBrowser : WebBrowser
    {
        public IWebBrowser2 axIWebBrowser2;

        protected override void AttachInterfaces(object nativeActiveXObject)
        {
            base.AttachInterfaces(nativeActiveXObject);
            this.axIWebBrowser2 = (IWebBrowser2) nativeActiveXObject;
        }

        protected override void DetachInterfaces()
        {
            base.DetachInterfaces();
            this.axIWebBrowser2 = null;
        }
    };
}

5.
Finally we create a Form and add the printing code there:

private void _btnPrint_Click(object sender, EventArgs e)
{
    IntPtr ptr = Marshal.GetComInterfaceForObject(
        webBrowser1.axIWebBrowser2,
        typeof(IWebBrowser2));
    PrintHelper.Print(ptr, "this is my header", "this is my footer");
}

Modified the header and footer:
NiceFooter

The Zip:
IEPrinting

References:
http://support.microsoft.com/kb/267240
http://thedotnet.com/nntp/97691/showpost.aspx