In an attempt to learn how to use this stuff, I've been writing a tabbed document editor. At the moment it only supports XML documents, and you can't actually... edit them, but the User Interface is what I'm developing here, at the moment.
The XAML source for the window above is:
Class
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Xml Editor" Height="570" Width="668"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
xmlns:my="clr-namespace:XmlEdit"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
Loaded="WindowLoaded" xml:lang="en-gb">
"ApplicationCommands.Close" Executed="AppCloseCommand"/>
"ApplicationCommands.New" Executed="DocNewCommand"/>
"ApplicationCommands.Save" Executed="DocSaveCommand" CanExecute="CanDocumentCommandExecute"/>
"ApplicationCommands.SaveAs" Executed="DocSaveAsCommand" CanExecute="CanDocumentCommandExecute"/>
"my:DocumentCommands.Close" Executed="DocCloseCommand" CanExecute="CanDocumentCommandExecute"/>
"my:DocumentCommands.Reload" Executed="DocReloadCommand" CanExecute="CanDocumentCommandExecute"/>
"VerticalSplitterStyle">
"FrameworkElement.Width" Value="5"/>
"documentList"/>
"DocumentHeaderTemplate">
"tabHeaderText" Text="{Binding Path=Title}"/>
"tabHeaderModifiedIndicator" Text=""/>
"{Binding Path=Saved}" Value="false">
"tabHeaderModifiedIndicator" Property="Text" Value="*"/>
"DocumentContentTemplate">
"{Binding Path=ContentElement}"/>
"Top">
"mainMenu" Background="Transparent">
"_File" >
"ApplicationCommands.New"/>
"ApplicationCommands.Save"/>
"ApplicationCommands.SaveAs"/>
"my:DocumentCommands.Close"/>
"my:DocumentCommands.Reload"/>
"ApplicationCommands.Close"/>
"tabs" ItemsSource="{Binding Source={StaticResource documentList}}"
ItemTemplate="{StaticResource DocumentHeaderTemplate}"
ContentTemplate="{StaticResource DocumentContentTemplate}"
>
"MiddleClick" Command="my:DocumentCommands.Close"/>
It seems fairly understandable without knowing any XAML. The TabControl is my favourite bit. Its ItemsSource property is bound to a DocumentList, which is derived from ObservableCollection. I then create an instance of this in the Window's ResourceDictionary (&;lt;Window.Resources>), which I can retrieve later in the codebehind file.
That is, you can use data binding with a Collection, and when the collection is updated, the UI is updated too. Also, each of the objects in the collection derives from INotifyPropertyChanged, which allows the UI to re-evaluate the DataTriggers on the template.
Commands are probably the most confusing part. Writing my own commands required writing a static class with RoutedUICommand members. They're then imported by mapping my XmlEdit namespace to the "my:" XML namespace, whereupon I can reference CLR classes and objects. Provided, for some reason, they're not in the same file as the actual codebehind. It doesn't like that at all.
The code to do this is simple... (irrelevant bits left out)
using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;using System.Windows.Controls.Primitives;namespace XmlEdit{ public partial class MainWindow : System.Windows.Window { private DocumentList documents; public MainWindow() { InitializeComponent(); //Apparently you can't use the x:Name attribute on elements in a Resource Dictionary, //so it seems it's not possible to have a member generated in the codebehind... Which //leaves us with this. documents = this.Resources["documentList"] as DocumentList; documents.Add(new XmlDocument()); } //Commands public void AppCloseCommand(object sender, ExecutedRoutedEventArgs e) { ((Window)sender).Close(); } public void AppNewWindowCommand(object sender, ExecutedRoutedEventArgs e) { new MainWindow(); } public void CanDocumentCommandExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = CurrentDocument != null; } public void DocSaveCommand(object sender, ExecutedRoutedEventArgs e) { IDocument doc = CurrentDocument; if (doc.Filename != null) { doc.Save(); } else { doc.SaveAs(); } } //Next problem: how to avoid mingling UI code (display a save dialog) //and document code... public void DocSaveAsCommand(object sender, ExecutedRoutedEventArgs e) { CurrentDocument.SaveAs(); } public void DocNewCommand(object sender, ExecutedRoutedEventArgs e) { IDocument doc = new XmlDocument(); documents.Insert(0, doc); //documents.Add(doc); tabs.SelectedIndex = 0; } //Close the current tab. Or, if the event was sent by middle-clicking on a tab, instead just close the tab which sent the event. public void DocCloseCommand(object sender, ExecutedRoutedEventArgs e) { IDocument doc; if (e.Source is TabItem) doc = (e.Source as TabItem).Content as IDocument; else doc = CurrentDocument; System.ComponentModel.CancelEventArgs ce = new System.ComponentModel.CancelEventArgs(); doc.Close(ce); if (!ce.Cancel) { documents.Remove(doc); } } //... //Convenience property. Useful, though. Should //possibly throw an exception if there isn't one... public IDocument CurrentDocument { get { return tabs.SelectedContent as IDocument; } } //I'm sure I was planning to use this private void WindowLoaded(object sender, EventArgs args) { } }}
It's nice and easy to understand, I think.
using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Text;using System.ComponentModel;using System.Collections.Specialized;namespace XmlEdit{ public interface IDocument: INotifyPropertyChanged { string Title { get; } bool Saved { get; } string Filename { get; set; } System.Windows.UIElement ContentElement { get; } void Close(System.ComponentModel.CancelEventArgs args); void Save(); void SaveAs(); event EventHandler Modified; event CancelEventHandler Closing; event EventHandler Closed; } class DocumentList : ObservableCollection { public DocumentList() { } }}
I'm using the WPF and WCF extensions for Visual Studio 2005, which are still a preview at the moment. This probably explains why visual studio is using about 350MB of RAM, and managed about 500MB before.
I really want to make a visual studio clone, because it has the coolest interface ever, but it still looks like that would be a lot of work [wink]. This is all certainly more fun than developing with WinForms.