Sunday, August 13, 2006

Switch view when drop down list has changed


It’s impossible to use the ViewInfos.SwitchView(string) method while the changed event is running. You can only switch a view by using the button clickhandler. This post will create a workaround to change view without clicking on buttons.

1. Using ViewInfos.SwitchView() method in the Changed event

While firing events it is impossible to switch to another view. Only the Click event on the button will permit a ‘SwitchView’ action. In all other cases you will receive a COMException at runtime.


public void InternalStartup()
{
EventManager.XmlEvents["/my:myFields/my:list"].Changed +=
new XmlChangedEventHandler(list_Changed);
}

public void field1_Changed(object sender, XmlEventArgs e)
{
ViewInfos.SwitchView("view2");
// The code is successfully compiled, but will not run !
// It results in a run-time error (COMException)
}

Redirecting to another view is not quite simple. We have to use other tricks


2. OnContextChange event is asynchronous

The OnContextChange event is fired when the form or elements on the forms has been changed. There is just a little problem the event is asynchronous. So, it does not fire on every change in the context node.

Instead, it fires the event when all other events has been terminated.We will use the OnContextChange event (which is only enabled if you created a form without browser-compatible features) to redirect to another view.Test the Asynchronous OnContextChange event:



public void InternalStartup()
{
EventManager.FormEvents.ContextChanged +=
new ContextChangedEventHandler(FormEvents_ContextChanged);
}

public void FormEvents_ContextChanged(object sender, ContextChangedEventArgs e)
{
System.Windows.Forms.MessageBox.Show("context changed");
}


While selecting several values in the drop down list, you will see that the OnContextChange event will not fire on every change in the context node.


More information:
Microsoft - OnContextChange event



3. Change the focus to force the OnContextChange event

We cannot trust on the OnContextChange event because it’s asynchronous. So we have to force the OnContextChange event by changing the focus from the drop down list to another field through code.

To change the focus we will use a textbox which must be visible and not read-only. You cannot set the focus to every control. More information :
http://blogs.msdn.com/infopath/archive/2004/04/07/109189.aspx

Following code will be executed while the ‘changed event’ of the drop down list has been fired.



public void list_Changed(object sender, XmlEventArgs e)
{
SetFocus("/my:myFields/my:unusedfield");
}

private void SetFocus(string xpath)
{
XPathNavigator fieldToFocus = MainDataSource.CreateNavigator().
SelectSingleNode(xpath,NamespaceManager);
this.CurrentView.SelectText(fieldToFocus);
}


Download the new InfoPath 2007 Object Model by this link:
http://download.microsoft.com/download/0/9/c/09cda3f2-6d3d-4082-aec5-9a62b7679ecf/Office2007InfoPathOMPoster.exe




4. Switch to another view

Now we can implement the OnContextChange method which allows to use the SwitchView() method.


public void FormEvents_ContextChanged(object sender, ContextChangedEventArgs e)
{
ViewInfos.SwitchView("view2");
}


The code is insufficient. The OnContextChange event will fire many times while filling in a form. It is not every time allowed to change the view. We need an extra variable which indicates if the SwitchView() method me be called.


private bool canRedirect = false;
public void FormEvents_ContextChanged(object sender, ContextChangedEventArgs e)
{
if (canRedirect)
{
canRedirect = false;
ViewInfos.SwitchView("view2");
}
}



5. Complete code


public partial class FormCode
{
public void InternalStartup()
{
EventManager.XmlEvents["/my:myFields/my:list"].Changed +=
new XmlChangedEventHandler(list_Changed);
EventManager.FormEvents.ContextChanged +=
new ContextChangedEventHandler(FormEvents_ContextChanged);
}


private bool canRedirect = false;
public void list_Changed(object sender, XmlEventArgs e)
{
canRedirect = true;
SetFocus("/my:myFields/my:unusedfield");
}

private void SetFocus(string xpath)
{
try
{
XPathNavigator fieldToFocus = MainDataSource.CreateNavigator().
SelectSingleNode(xpath,NamespaceManager);
this.CurrentView.SelectText(fieldToFocus);
}
catch (ArgumentException ex)
{
MessageBox.Show("The focus cannot be set to " + xpath);
}
}

public void FormEvents_ContextChanged(object sender, ContextChangedEventArgs e)
{
if (canRedirect)
{
canRedirect = false;
ViewInfos.SwitchView("view2");
}
}
}




6. Tips
You can hide the ‘canRedirect’ member by writing a wrapper class around this member