Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    12
  • comments
    26
  • views
    17615

My new found love: XAML

Sign in to follow this  
MrEvil

496 views

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"/>






"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.
Sign in to follow this  


2 Comments


Recommended Comments

Thanks. I had to use WinForms recently for a university project, and it felt rather odd... like going back to VB6 from C# [grin]

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!