Archive for the ‘Presentation’ Category

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>");

	// 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: %lXn"), 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: %lXn"), 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

Assemblers with ConvertAll

Friday, September 18th, 2009

In most applications we have some kind of assembler classes, that create models used in the presentation layer:

public class EntityComboModelAssembler
{
   public List<comboBoxModel> CreateComboBoxModels(IList<entity> entities)
   {
      List<comboBoxModel> list = new List<comboBoxModel>();
      foreach (Entityentity in entities)
      {
         list.Add(new ComboBoxModel(entity, entity.Name));
      }
      return list;
   }
}

It is possible to write this code in 1 (one) line of code:

public class EntityComboModelAssembler
{
   public List<comboBoxModel> CreateComboBoxModels(IList<entity> entities)
   {
      return entities.AsList().ConvertAll(x => new ComboBoxModel(x, x.Name));
   }
}

We can also remove AsList method by using extension method that adds ConvertAll method to IList interface:

public static class IListExtensions
{
   public static List<tout> ConvertAll<tin, TOut>(this IList<tin> source, Converter<tin, TOut> converter)
   {
      return source.ToList().ConvertAll(converter);
   }
}

And the final code:

public class EntityComboModelAssembler
{
   public List<comboBoxModel> CreateComboBoxModels(IList<entity> entities)
   {
      return entities.ConvertAll(x => new ComboBoxModel(x, x.Name));
   }
}

INotifyPropertyChanged with PostSharp 1.5

Tuesday, September 15th, 2009

If you are doing WPF development, most likely you are tired of writing property implementations that raise PropertyChanged event manually:

public class MainWindowViewModel : ViewModel
{
    private string _message;

    public string Message
    {
        get
        {
            return _message;
        }
        set
        {
            _message = value;
            OnPropertyChanged("Message");
        }
    }

    // ...
}

PostSharp is a great tool to make such things simplier.

Let’s look at the specific ViewModel class that has a Message property that is bound to some UI element using XAML:

public class MainWindowViewModel : ViewModel
{
    [RaisePropertyChanged]
    public string Message { get; set; }

    // ...
}

Notice the RaisePropertyChanged attribute, which we’ll implement later.

Here’s our base ViewModel class that provides actual implementation of the INotifyPropertyChanged interface:

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged
        = delegate { };

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
    }
};

Finally the PostSharp attribute:

[Serializable]  // required by PostSharp
public class RaisePropertyChangedAttribute : OnMethodBoundaryAspect
{
    private string _propertyName;

    /// <summary>
    /// Executed at runtime, after the method.
    /// </summary>
    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        ViewModel viewModel = (ViewModel)eventArgs.Instance;
        viewModel.OnPropertyChanged(_propertyName);
    }

    public override bool CompileTimeValidate(MethodBase method)
    {
        if (IsPropertySetter(method))
        {
            _propertyName = GetPropertyName(method);
            return true;
        }
        return false;
    }

    private static string GetPropertyName(MethodBase method)
    {
        return method.Name.Replace"set_", "");
    }

    private static bool IsPropertySetter(MethodBase method)
    {
        return method.Name.StartsWith("set_");
    }
};

Note that we are validating if the method is in fact a property only during compilation time using CompileTimeValidate method.

During compile time appropriate invocations of OnPropertyChanged method will be injected after every set operation applied to the Message property.