October 2006 - Posts
So what does InvalidateVisual do? The SDK documentation says it "invalidates the rendering of the element, and forces a complete new layout pass. OnRender is called after the layout cycle is completed". Just what I needed when it was time to implement scrolling for a custom control shown below - or so I thought.

The custom horizontal scrollbar at the bottom of the control is bound to an ICommand whose implementation is shown below:
class ScrollCommand : BaseCommand
{
public ScrollCommand(Controller controller)
: base(controller)
{
}
public override void Execute(object parameter)
{
double value = (double)parameter;
controller.horScroll = value;
controller.designer.InvalidateVisual();
}
}
The Execute method will force the UIElements Field1, Field2 and Field3 to be repositioned in their respective canvas container relative to the value of the scrollbar by calling the host control's InvalidateVisual method. This provides the illusion of scrolling. It was a dud.
I was beginning to doubt this whole WPF Binding thing when I chanced upon the Measure method. The SDK describes this as "Updates the DesiredSize of a UIElement. Parent elements call this method from their own MeasureCore implementations to form a recursive layout update. Calling this method constitutes the first pass of a layout update in the layout engine". It looked very promising, so i replaced the call to InvalidateVisual with Measure as show below:
public override void Execute(object parameter)
{
double value = (double)parameter;
controller.horScroll = value;
controller.designer.Measure(new Size());
}
Needless to say it worked!
After reading this post by Karsten, I created a WPF XBAP project that displays the ClassicReports designer on a Frame as shown below.
<Page x:Class="IApp.Page3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page3"
>
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Name="newButton">
<Image Source="images\new.png" />
</Button>
<Button Name="openButton" >
<Image Source="images\open.png" />
</Button>
<Button Name="saveButton" >
<Image Source="images\save.png" />
</Button>
</ToolBar>
</ToolBarTray>
<Frame Name="frame1"></Frame>
</DockPanel>
</Page>
Below is the c# code behind for the XAML above. Notice that the designer control must first be hosted in a WindowsFormsHost before it can be displayed in WPF.
namespace IApp
{
public partial class Page3 : System.Windows.Controls.Page
{
JDJ.ClassicReport.ReportDesigner.ReportDesigner designer;
public Page3()
{
InitializeComponent();
WindowsFormsHost host = new WindowsFormsHost();
designer = new JDJ.ClassicReport.ReportDesigner.ReportDesigner();
host.Child = designer;
frame1.Content = host;
openButton.Click += new RoutedEventHandler(openButton_Click);
saveButton.Click += new RoutedEventHandler(saveButton_Click);
newButton.Click += new RoutedEventHandler(newButton_Click);
}
void newButton_Click(object sender, RoutedEventArgs e)
{
designer.NewReport();
}
void saveButton_Click(object sender, RoutedEventArgs e)
{
designer.Save();
}
void openButton_Click(object sender, RoutedEventArgs e)
{
designer.OpenReport();
}
}
}
This is how the above page looks like. Notice the address I used in runnning the XBAP application. That's my actual IP address as of this writing.
When I clicked on the Open button the dialog box appears.
So after selecting the Sample Report File, the designer then displays the report layout on the design surface. Now I can modify the report to my heart's content.
I almost forgot that I am actually running this rich client application inside a browser!
The ListCollectionView is a WPF wrapper for an IList. When you add or remove something from the IList, the ListCollectionView must be notified of the change by calling its OnCollectionChanged method so it can sync with the IList.
The problem is, OnCollectionChanged is a protected method i.e. we can't access it from outside of a ListViewCollection object. This means we have to create a new class that inherits from ListCollectionView so we can have the means to call OnCollectionChanged whenever we add or remove something from the IList object we are wrapping e.g.
class StudentListView(ListCollectionView):
def constructor(theClass as Class):
super(theClass.StudentList)
theClass.StudentListChanged += OnStudentListChanged
def OnStudentListChanged(sender as object, e as StudentListChangedEventArgs):
if e.ChangeMode == Class.ChangeMode.Add:
args = NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,e.Student,e.Index)
else
args = NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,e.Student,e.Index)
OnCollectionChanged(args)
So whenever the parent Class object fires its StudentListChanged event after adding or removing a Student from its StudentList, then the StudentListView object gets to call the OnCollectionChanged method it inherited from ListCollectionView, allowing it to remain in sync with the underlying StudentList object.
About two nights ago, I learned how to use an external XAML in a WPF application. It was so simple. Just read the XAML, parse it, and then wire the elements to your code. Now if this is how we are supposed to code in WPF, then I might just as well stick to WinForms.
Fortunately XAML was not designed like Winforms or even Webforms. The FrameworkElement in WPF has a DataContext property which is an object type. This means we can feed it any kind of object that fits our purpose. The great thing about this is, XAML has the capability to bind all the elements in the object tree to the root element's DataContext property. All we have to do is bind an element's property or properties to the corresponding property or properties of the object concerned like so:
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ListBox
Name="listBox1"
IsSynchronizedWithCurrentItem="true"
ItemsSource="{Binding Path=Employees}"/>
<Button
CommandParameter="{Binding ElementName=listBox1}"
Command="{Binding Path=DeleteEmployee}"
Content="Delete"/>
</StackPanel>
Assuming we have a class called CompanyViewController that exposes the properties Employees (ListCollectionView) and DeleteEmployee (ICommand), and the XAML is stored in EmployeeListView.xaml, the code to drive this XAML could simply look like this:
class EmployeeListView(Window):
def constructor(company as Company):
# parse the element tree via the XamlReader
streamReader = StreamReader("EmployeeListView.xaml")
xmlreader = XmlReader.Create(streamReader)
view as Panel= XamlReader.Load(xmlreader)
# give the view something to chew on
view.DataContext = CompanyViewController(company)
# display the view in the window
self.Content = view
If you are a TDD kind of guy, you have to feel that tingling sensation!
My first impression with XAML was that you get to play with it only if your building WPF apps in c#. It seems the .NET3 c# compiler is the only one capable of compiling XAML markup and code into an executable file. It was a big letdown for Boo aficionados like me since XAML is clearly the way to go when it comes to creating user interfaces in WPF.
I was dead wrong. It seems any .NET language like Boo can still leverage XAML with a simple workaround as illustrated by Rob Relyea on his post about the 3 Coding Styles for Avalon Applications.
To see if Rob's workaround will work, I wrote the following markup in XamlPad and copy pasted it to a file called "Exer1.xaml" in my Boo project. I didn't want the Boo compiler to touch this markup at compile time so I set its Build action property to None and its Copy to output directory property to PreserveNewest.
<StackPanel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Margin="10">
<DockPanel Width="300" HorizontalAlignment="Left" Margin="10 0 0 5">
<StackPanel DockPanel.Dock="Left">
<TextBox
Name="textBox1"
Width="230"
FontSize="12"
FontFamily="Tahoma"
Margin="0 0 2 2"/>
</StackPanel>
<Button
Name="addButton"
DockPanel.Dock="Right"
VerticalAlignment="Top"
Width="60"
Content="Add"/>
</DockPanel>
<DockPanel HorizontalAlignment="Left" Width="300" Margin="10 0 0 0">
<StackPanel DockPanel.Dock="Left">
<ListBox
Name="listBox1"
Width="230"
Height="100"
FontSize="12"
FontFamily="Tahoma"
Background="GhostWhite"
Margin="0 0 2 2">
</ListBox>
</StackPanel>
<Button
Name="delButton"
DockPanel.Dock="Right"
VerticalAlignment="Top"
Width="60"
Content="Delete"/>
</DockPanel>
</StackPanel>
Next, I added the following Boo code to the project. The logic of the program is simple. When you click on the Add button (addButton), whatever you enter in the TextBox (textBox1) is placed on the ListBox (listBox1) below it. When you click on the Delete button (delButton), the selected item on the listbox will be removed.
namespace WinFx.Demo
import System
import System.Collections
import System.Windows
import System.Windows.Controls
import System.Windows.Controls.Primitives
import System.Windows.Input
import System.Windows.Media
import System.Windows.Markup
import System.Windows.Shapes
import System.IO
import System.Xml
class Exer1(Window):
textBox1 as TextBox
listBox1 as ListBox
addButton as Button
delButton as Button
def constructor():
# parse the element tree via the XamlReader
streamReader = StreamReader("Exer1.xaml")
xmlreader = XmlReader.Create(streamReader)
view as Panel= XamlReader.Load(xmlreader)
# get a reference to the named elements via the LogicalTreeHelper
textBox1 = LogicalTreeHelper.FindLogicalNode(view, "textBox1")
listBox1 = LogicalTreeHelper.FindLogicalNode(view, "listBox1")
addButton = LogicalTreeHelper.FindLogicalNode(view, "addButton")
delButton = LogicalTreeHelper.FindLogicalNode(view, "delButton")
# wire up the elements to your code
addButton.Click += AddButtonClick
delButton.Click += DelButtonClick
# customize the window
self.Content = view
self.WindowStartupLocation = WindowStartupLocation.CenterScreen
self.Title = "XAML Meets Boo"
self.Width = 500
self.Height = 300
protected def AddButtonClick(sender as object, e as RoutedEventArgs):
listBox1.Items.Add(textBox1.Text)
textBox1.Text = ""
textBox1.Focus()
protected def DelButtonClick(sender as object, e as RoutedEventArgs):
index = listBox1.SelectedIndex
if index >= 0:
listBox1.Items.RemoveAt(index)
[STAThread]
def Main():
app = Application()
Exer1().Show()
app.Run()
Here's a screenshot of this in action.

The code above looks very much like a regular WinForm code except the contents of the window are defined in an external XAML file. Shades of ASP.NET? One of the neat things about this scenario is that I can retouch the XAML sometime later using Sparkle for some eyecandy effects and then run the same app again with no recompilation whatsoever.