My new found love: XAML

posted in MrEvil's Journal
Published November 16, 2006
Advertisement
I recently discovered XAML, eXtensible Application Markup Language, when the .NET 3.0 release completely took me by surprise. I didn't even think this stuff would be released on Windows XP!

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
="XmlEdit.MainWindow"
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.
Previous Entry Boredom
0 likes 2 comments

Comments

paulecoyote
interesting post [smile]
November 28, 2006 03:16 AM
MrEvil
Thanks. I had to use WinForms recently for a university project, and it felt rather odd... like going back to VB6 from C# [grin]
November 28, 2006 07:07 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement

Latest Entries

What?

1180 views

DS homebrew

1352 views

Boredom

1354 views

Chef

1145 views

New journal

1047 views

Erm

1248 views

My System Library

1109 views

Befunge!

1519 views
Advertisement