Developer Preview Warning
This article and its contents are based on the first public developer
preview of Windows 8 and Visual Studio 11. The code snippets and other
information mentioned in the article may thus be subject to significant changes
when the OS/VS hits beta/RTM.
Introduction
Metro/WinRT supports powerful databinding capabilities exposed through XAML
(you can do everything in straight code too, but XAML is the recommended
approach). The databinding support in WinRT differs from that used in WPF/Silverlight
in its implementation although externally this is not so obvious when you use C#
or VB. This is because the .NET interop code implements the required thunking/plumbing
needed to smoothly transition from standard .NET code into WinRT databinding.
This is not so for C++ as of the developer preview. C++ developers need to
implement certain interfaces to get their classes to participate in databinding.
They may change this in a future release. So this
article should be looked at as one of primarily academic interest.
How you'd expect it to work
Here's some minimal C# code that shows how a TextBlock
control's
Text
property is bound to a String
property.
partial class MainPage
{
public MainPage()
{
InitializeComponent();
this.DataContext = new Data();
}
}
class Data
{
public string Title
{
get
{
return "data binding!";
}
}
}
And here's the XAML.
<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
Data
is a regular C# class and Title
is a standard
C# property. No extra work was needed to make the class databindable. I expected
a similar experience when using C++ (except for the syntactic changes). Suffice
it to say that I was a little disappointed to find that it was not that
straightforward. Again, I do hope that this will be made simpler in a future release. Until then, if you want to play with databinding or just get a hang of how
things work, you'll need to add some extra code to your classes.
WinRT databinding
WinRT uses COM based databinding, and any databindable code is expected to
implement the ICustomProperty
interface.
MIDL_INTERFACE("30DA92C0-23E8-42A0-AE7C-734A0E5D2782")
ICustomProperty : public IInspectable
{
public:
virtual HRESULT STDMETHODCALLTYPE get_Type(
__RPC__out Windows::UI::Xaml::Interop::TypeName *value) = 0;
virtual HRESULT STDMETHODCALLTYPE get_Name(
__RPC__deref_out_opt HSTRING *value) = 0;
virtual HRESULT STDMETHODCALLTYPE GetValue(
__RPC__in_opt IInspectable *obj,
__RPC__deref_out_opt IInspectable **value) = 0;
virtual HRESULT STDMETHODCALLTYPE SetValue(
__RPC__in_opt IInspectable *obj,
__RPC__in_opt IInspectable *value) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIndexedValue(
__RPC__in_opt IInspectable *obj,
__RPC__in_opt IInspectable *index,
__RPC__deref_out_opt IInspectable **value) = 0;
virtual HRESULT STDMETHODCALLTYPE SetIndexedValue(
__RPC__in_opt IInspectable *obj,
__RPC__in_opt IInspectable *value,
__RPC__in_opt IInspectable *index) = 0;
virtual HRESULT STDMETHODCALLTYPE get_CanWrite(
__RPC__out boolean *value) = 0;
virtual HRESULT STDMETHODCALLTYPE get_CanRead(
__RPC__out boolean *value) = 0;
};
Here's a simpler .NET-ish version of the interface (easier to absorb).
[Guid("30DA92C0-23E8-42A0-AE7C-734A0E5D2782")]
interface ICustomProperty
{
Type Type { get; }
string Name { get; }
object GetValue(object target);
void SetValue(object target, object value);
object GetIndexedValue(object target, object indexValue);
void SetIndexedValue(object target, object value, object indexValue);
bool CanRead { get; }
bool CanWrite { get; }
}
In theory all your databindable classes need this interface. In practice, it
would be quite impractical and absurd to have to do that. So a good start would
be to declare a class that implements this interface.
Implementing some base and helper classes for databinding
I studied the .NET implementation and saw that they have used
PropertyInfo
(the .NET reflection class that represents a property) in
their implementation. Since WinRT does not have an equivalent, I thought that's
what I should first write. And I chose the same name being of a rather
unimaginative nature myself.
namespace ProjectInternal
{
delegate Object^ PropertyGetValue(Object^ target);
delegate void PropertySetValue(Object^ target, Object^ value);
}
ref class PropertyInfo sealed
{
ProjectInternal::PropertyGetValue^ _propertyGetValue;
ProjectInternal::PropertySetValue^ _propertySetValue;
public:
PropertyInfo(String^ name, String^ typeName,
ProjectInternal::PropertyGetValue^ propertyGetValue,
ProjectInternal::PropertySetValue^ propertySetValue)
{
this->Name = name;
TypeName temp;
temp.Name = typeName;
temp.Kind = TypeKind::Primitive;
this->Type = temp;
_propertyGetValue = propertyGetValue;
_propertySetValue = propertySetValue;
}
property String^ Name;
property TypeName Type;
property bool CanRead
{
bool get()
{
return _propertyGetValue != nullptr;
}
}
property bool CanWrite
{
bool get()
{
return _propertySetValue != nullptr;
}
}
Object^ GetValue(Object^ target)
{
return _propertyGetValue(target);
}
void SetValue(Object^ target, Object^ value)
{
_propertySetValue(target, value);
}
};
The class basically wraps a property, and allows a caller to get or set
values. And to do that it uses delegates (declared above). For the sake of
simplicity (and also because I was lazy), I do not consider indexed properties
(not commonly used in XAML anyway). Since we now have a
PropertyInfo
class, implementing the
ICustomProperty
class is easy.
ref class CustomProperty sealed : public ICustomProperty
{
PropertyInfo^ _propertyInfo;
public:
CustomProperty(PropertyInfo^ propertyInfo)
{
_propertyInfo = propertyInfo;
}
virtual property TypeName Type
{
virtual TypeName get()
{
return _propertyInfo->Type;
}
}
virtual property String^ Name
{
virtual String^ get()
{
return _propertyInfo->Name;
}
}
virtual property bool CanRead
{
virtual bool get()
{
return _propertyInfo->CanRead;
}
}
virtual property bool CanWrite
{
virtual bool get()
{
return _propertyInfo->CanWrite;
}
}
virtual Object^ GetValue(Object^ target)
{
return _propertyInfo->GetValue(target);
}
virtual void SetValue(Object^ target, Object^ value)
{
_propertyInfo->SetValue(target, value);
}
virtual void SetIndexedValue(Object^ target, Object^ value, Object^ index)
{
throw ref new NotImplementedException();
}
virtual Object^ GetIndexedValue(Object^ target, Object^ value)
{
throw ref new NotImplementedException();
}
};
Again note how indexed properties are not supported. Now that we have an
implementation for ICustomProperty
we also need an implementation
for ICustomPropertyProvider
. In fact the simplest way you can make
an object databindable is to have it implement this interface.
MIDL_INTERFACE("7C925755-3E48-42B4-8677-76372267033F")
ICustomPropertyProvider : public IInspectable
{
public:
virtual HRESULT STDMETHODCALLTYPE GetCustomProperty(
__RPC__in HSTRING name,
__RPC__deref_out_opt Windows::UI::Xaml::Data::ICustomProperty **property) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIndexedProperty(
__RPC__in HSTRING name,
Windows::UI::Xaml::Interop::TypeName type,
__RPC__deref_out_opt Windows::UI::Xaml::Data::ICustomProperty **property) = 0;
virtual HRESULT STDMETHODCALLTYPE GetStringRepresentation(
__RPC__deref_out_opt HSTRING *stringRepresentation) = 0;
virtual HRESULT STDMETHODCALLTYPE get_Type(
__RPC__out Windows::UI::Xaml::Interop::TypeName *value) = 0;
};
Minimally, you need to implement GetCustomProperty
and the
Type
property. You can leave the other methods blank (or at least you can
for anything demonstrated in this article).
ref class CustomPropertyProviderBase abstract
: public ICustomPropertyProvider,
public INotifyPropertyChanged,
public IDisposable
{
std::map<String^, ICustomProperty^> customPropertyMap;
bool customPropertiesAdded;
public:
virtual ICustomProperty^ GetCustomProperty(String^ name)
{
if(!customPropertiesAdded)
{
AddCustomProperties();
customPropertiesAdded = true;
}
auto it = customPropertyMap.find(name);
return it == customPropertyMap.end() ? nullptr : it->second;
}
virtual ICustomProperty^ GetIndexedProperty(String^ name, TypeName typeName)
{
throw ref new NotImplementedException();
}
virtual String^ GetStringRepresentation()
{
throw ref new NotImplementedException();
}
virtual property TypeName Type
{
TypeName get()
{
TypeName temp;
temp.Name = "CustomPropertyProviderBase";
temp.Kind = TypeKind::Primitive;
return temp;
}
}
void AddCustomProperty(String^ name, String^ typeName,
ProjectInternal::PropertyGetValue^ propertyGetValue,
ProjectInternal::PropertySetValue^ propertySetValue)
{
customPropertyMap[name] = ref new CustomProperty
ref new PropertyInfo(name, typeName, propertyGetValue, propertySetValue));
}
virtual void AddCustomProperties() = 0;
event PropertyChangedEventHandler^ PropertyChanged;
protected:
void FirePropertyChanged(String^ name)
{
if(PropertyChanged != nullptr)
{
PropertyChanged(this, ref new PropertyChangedEventArgs(name));
}
}
};
I decided to make this my base class for all my WinRT business objects (that
would participate in databinding). Hence I have also implemented
IDisposable
(compiler generated) and
INotifyPropertyChanged
.
Please note that
INotifyPropertyChanged
is a COM version of the
interface of the same name used by Silverlight, WPF, and Windows Forms. It serves
the same purpose too - which is to notify listeners of changes in properties.
The GetCustomProperty
method returns an appropriate
ICustomProperty
object for a specific property. The properties are not
added here (this is a base class after all), but are instead added by derived
classes. To make it easier for derived classes, there's an
AddCustomProperty
method provided that they can use. There is a virtual
AddCustomProperties
method that derived classes must implement
where they can add any data-bound properties.
One rather annoying thing here is that you cannot directly pass a property's
getter or setter to create the delegate. Instead you have to wrap a method for
the getter and another for the setter and then use that for the delegate. This
would be quite ridiculous for any reasonably serious business object. To avoid
doing that I wrote two macros that use lambdas to generate the required
delegates. Of all the things I did here this made me smile, since I was mixing
macros (C/C++ feature for decades) with lambdas (modern C++ feature) to improve
simplicity.
#define MAKEPROPGET(class, name) ref new ProjectInternal::PropertyGetValue(
[](Object^ instance) {return ((class^)instance)->name;})
#define MAKEPROPSET(class, name, type) ref new ProjectInternal::PropertySetValue(
[](Object^ instance, Object^ value) {((class^)instance)->name = (type)value;})
Databinding works now!
Here's a very simple C++ business object bound to a Xaml view.
ref class MainViewModel sealed : public CustomPropertyProviderBase
{
String^ title;
. . .
property String^ Title
{
String^ get()
{
return title;
}
void set(String^ value)
{
if(title != value)
{
title = value;
FirePropertyChanged("Title");
}
}
}
virtual void AddCustomProperties() override
{
AddCustomProperty("Title", "String",
MAKEPROPGET(MainViewModel, Title),
MAKEPROPSET(MainViewModel, Title, String^));
}
And here's the XAML.
<TextBlock x:Name="title" FontSize="24" TextAlignment="Left" Text="{Binding Title}" />
Did that work or what? Who's your daddy now, WinRT? :-)
The example application
I have used the MVVM pattern (loosely speaking) for the demo project. The
model consists of a C++ class that's exposed as a std::list
, the
view-model is a WinRT wrapper (that supports data-binding), and the view is all
XAML. The model data is not persisted on disk in the demo. In a real world
scenario, you may fetch data from a legacy C++ library, SQL server, a JSON
web-service or even a plain-text data file. I'll run through some of the code
snippets to show some typical C++ to WinRT type conversions and to also give a
quick overview of how the C++ data is bound to the XAML view via a WinRT middle
layer. Here's what the C++ model class (a struct
rather) looks
like.
extern int nextId;
struct Bookmark
{
int id;
public:
std::wstring Author;
std::wstring Title;
std::wstring Url;
Bookmark(std::wstring author, std::wstring title, std::wstring url)
: Author(author), Title(title), Url(url)
{
id = nextId++;
}
int GetId()
{
return id;
}
};
Note how the data-properties are of type std::wstring
(a type
not recognized by WinRT/COM). And here's a manager-class that is used by the
view-model.
class BookmarkModel
{
std::list<Bookmark> bookmarks;
public:
BookmarkModel();
Bookmark AddNew(std::wstring author, std::wstring title, std::wstring url)
{
Bookmark bookmark(author, title, url);
bookmarks.push_back(bookmark);
return bookmark;
}
void Delete(int id)
{
bookmarks.remove_if([=] (const Bookmark& bookmark) { return bookmark.id == id; });
}
void Update(int id, std::wstring author, std::wstring title, std::wstring url)
{
auto it = std::find_if(bookmarks.begin(), bookmarks.end(),
[=] (const Bookmark& bookmark){ return bookmark.id == id; });
if(it != bookmarks.end())
{
it->Author = author;
it->Title = title;
it->Url = url;
}
}
std::list<Bookmark> GetBookmarks()
{
return bookmarks;
}
};
The manager class has all the CRUD operations. Notice the use of lambdas
there, making the code pretty similar to what equivalent C# would have looked
like. The model classes here are completely WinRT independent. I'd assume that
that's how it would be for the vast majority of external data sources.
The view is a templated Metro ListBox
.
<ListBox Visibility="{Binding ShowListBox}" ItemsSource="{Binding Bookmarks}"
Background="Transparent" Width="440" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="10" Width="400">
<StackPanel.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="#FF8CA5D8" Offset="0.811"/>
</LinearGradientBrush>
</StackPanel.Background>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Author:" FontSize="16"
Foreground="AntiqueWhite" Margin="0,0,2,0" />
<TextBlock Text="{Binding Author}" Foreground="AntiqueWhite" FontSize="16" />
</StackPanel>
<TextBlock Text="{Binding Title}" FontSize="16" />
<TextBlock x:Name="url" Tag="{Binding}" Text="{Binding Url}" FontSize="16" />
<StackPanel Orientation="Horizontal"
DataContext="{Binding ElementName=LayoutRoot,Path=DataContext}">
<Button Command="{Binding OpenCommand}"
CommandParameter="{Binding ElementName=url,Path=Text}" Content="Open" />
<Button Command="{Binding DeleteCommand}"
CommandParameter="{Binding ElementName=url,Path=Tag}">Delete</Button>
<Button Command="{Binding EditCommand}"
CommandParameter="{Binding ElementName=url,Path=Tag}">Edit</Button>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Notice all the data-bindings that are defined in the XAML. If you've never
used XAML before, it's going to take you a while to get used to defining UI in
this manner. It's a lot easier if you've done any WPF or Silverlight. The editor
panel is very similar.
<StackPanel Visibility="{Binding ShowEditor}" x:Name="editor" Width="700"
HorizontalAlignment="Left"
Orientation="Vertical" Margin="10" Background="DarkGreen">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock FontSize="16" Width="70" >Author:</TextBlock>
<TextBox Text="{Binding EditAuthor, Mode=TwoWay}" Width="500" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock FontSize="16" Width="70">Title:</TextBlock>
<TextBox Text="{Binding EditTitle, Mode=TwoWay}" Width="500" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock FontSize="16" Width="70">Url:</TextBlock>
<TextBox Text="{Binding EditUrl, Mode=TwoWay}" Width="500" />
</StackPanel>
<Button Command="{Binding SaveCommand}">Save</Button>
</StackPanel>
One thing to note here is how the bindings are two-way here. This is because
I also want the UI changes to propagate back to the business objects. You
probably must have noticed all the command bindings in there. To facilitate
this, I used a very simple ICommand
implementation. Anyone who's
used any form of MVVM before would recognize it as a standard command class with
execute and can-execute methods.
delegate void ExecuteDelegate(Object^ parameter);
delegate bool CanExecuteDelegate(Object^ parameter);
ref class DelegateCommand sealed : public ICommand
{
ExecuteDelegate^ _executeDelegate;
CanExecuteDelegate^ _canExecuteDelegate;
public:
DelegateCommand(ExecuteDelegate^ executeDelegate,
CanExecuteDelegate^ canExecuteDelegate = nullptr)
{
_executeDelegate = executeDelegate;
_canExecuteDelegate = canExecuteDelegate;
}
event EventHandler^ CanExecuteChanged;
void Execute(Object^ parameter)
{
_executeDelegate(parameter);
}
bool CanExecute(Object^ parameter)
{
return _canExecuteDelegate == nullptr || _canExecuteDelegate(parameter);
}
};
Hooking up the view-model
The most important thing to do (outside of defining the actual properties and
commands) is to add them to property chain (which then enables data binding on
those properties). I spent many a foolish moment pondering what the heck was
wrong before realizing that I had forgotten to add an entry for a newly added
property.
virtual void AddCustomProperties() override
{
AddCustomProperty("Title", "String",
MAKEPROPGET(MainViewModel, Title),
MAKEPROPSET(MainViewModel, Title, String^));
AddCustomProperty("UpdateTitleCommand", "ICommand",
MAKEPROPGET(MainViewModel, UpdateTitleCommand),
MAKEPROPSET(MainViewModel, UpdateTitleCommand, ICommand^));
AddCustomProperty("Bookmarks", "IObservableVector<Object>",
MAKEPROPGET(MainViewModel, Bookmarks), nullptr);
AddCustomProperty("OpenCommand", "ICommand",
MAKEPROPGET(MainViewModel, OpenCommand),
MAKEPROPSET(MainViewModel, OpenCommand, ICommand^));
AddCustomProperty("EditCommand", "ICommand",
MAKEPROPGET(MainViewModel, EditCommand),
MAKEPROPSET(MainViewModel, EditCommand, ICommand^));
AddCustomProperty("DeleteCommand", "ICommand",
MAKEPROPGET(MainViewModel, DeleteCommand),
MAKEPROPSET(MainViewModel, DeleteCommand, ICommand^));
AddCustomProperty("SaveCommand", "ICommand",
MAKEPROPGET(MainViewModel, SaveCommand),
MAKEPROPSET(MainViewModel, SaveCommand, ICommand^));
AddCustomProperty("AddCommand", "ICommand",
MAKEPROPGET(MainViewModel, AddCommand),
MAKEPROPSET(MainViewModel, AddCommand, ICommand^));
AddCustomProperty("ShowListBox", "String",
MAKEPROPGET(MainViewModel, ShowListBox),
MAKEPROPSET(MainViewModel, ShowListBox, String^));
AddCustomProperty("ShowEditor", "String",
MAKEPROPGET(MainViewModel, ShowEditor), nullptr);
AddCustomProperty("EditAuthor", "String",
MAKEPROPGET(MainViewModel, EditAuthor),
MAKEPROPSET(MainViewModel, EditAuthor, String^));
AddCustomProperty("EditTitle", "String",
MAKEPROPGET(MainViewModel, EditTitle),
MAKEPROPSET(MainViewModel, EditTitle, String^));
AddCustomProperty("EditUrl", "String",
MAKEPROPGET(MainViewModel, EditUrl),
MAKEPROPSET(MainViewModel, EditUrl, String^));
}
That's the boring repetitive bit that I personally hope they will automate for us in a future release. A typical data property would like this.
property String^ EditAuthor
{
String^ get()
{
return editAuthor;
}
void set(String^ value)
{
if(editAuthor != value)
{
editAuthor = value;
FirePropertyChanged("EditAuthor");
}
}
}
That's regular looking code there. Except for the call to
FirePropertyChanged
(which is needed to tell WinRT that a property was
updated). The commands are similar except they need to be hooked up to the right
methods.
OpenCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &MainViewModel::Open));
EditCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &MainViewModel::Edit));
DeleteCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &MainViewModel::Delete));
SaveCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &MainViewModel::Save));
AddCommand = ref new DelegateCommand(ref new ExecuteDelegate(this, &MainViewModel::Add));
The important bits in the rest of the code have mostly to do with the WinRT
C++ interaction between the view-model and the model. Here's an example of how
the data is read.
void LoadData()
{
auto items = bookmarkModel.GetBookmarks();
for(auto it = items.begin(); it != items.end(); it++)
{
Bookmarks->Append(ref new BookmarkViewModel(*it));
}
}
BookmarkViewModel
is the view-model databindable wrapper that
represents a C++ Bookmark
object.
ref class BookmarkViewModel sealed : public CustomPropertyProviderBase
{
String^ author;
String^ title;
String^ url;
public:
BookmarkViewModel(Bookmark& bookmark)
{
Id = bookmark.GetId();
Author = ref new String(bookmark.Author.c_str());
Title = ref new String(bookmark.Title.c_str());
Url = ref new String(bookmark.Url.c_str());
}
property int Id;
property String^ Author
{
. . .
}
property String^ Title
{
. . .
}
property String^ Url
{
. . .
}
virtual void AddCustomProperties() override
{
AddCustomProperty("Author", "String",
MAKEPROPGET(BookmarkViewModel, Author),
MAKEPROPSET(BookmarkViewModel, Author, String^));
AddCustomProperty("Title", "String",
MAKEPROPGET(BookmarkViewModel, Title),
MAKEPROPSET(BookmarkViewModel, Title, String^));
AddCustomProperty("Url", "String",
MAKEPROPGET(BookmarkViewModel, Url),
MAKEPROPSET(BookmarkViewModel, Url, String^));
}
};
The constructor converts types as needed and we also hook up the
custom properties that will participate in data-binding. Here's how the delete
command is implemented.
void Delete(Object^ parameter)
{
BookmarkViewModel^ bookmarkViewModel = (BookmarkViewModel^)parameter;
unsigned int index;
if(Bookmarks->IndexOf(bookmarkViewModel, index))
{
try
{
Bookmarks->RemoveAt(index);
bookmarkModel.Delete(bookmarkViewModel->Id);
}
catch(...)
{
}
}
}
Ignore the try
-catch
there (that's to work around a
bug in the dev preview). Note how separate deletion calls are made into the
WinRT v-m and to the C++ model.
Edit
and Add
work similarly. They both bring up the
editor panel and delegate the actual save-task to the Save
method.
void Edit(Object^ parameter)
{
BookmarkViewModel^ bookmarkViewModel = (BookmarkViewModel^)parameter;
Bookmarks->IndexOf(bookmarkViewModel, editIndex);
editId = bookmarkViewModel->Id;
this->EditAuthor = bookmarkViewModel->Author;
this->EditTitle = bookmarkViewModel->Title;
this->EditUrl = bookmarkViewModel->Url;
this->ShowListBox = "Collapsed";
}
void Add(Object^ parameter)
{
editId = -1;
this->EditAuthor = "";
this->EditTitle = "";
this->EditUrl = "";
this->ShowListBox = "Collapsed";
}
void Save(Object^ parameter)
{
if(editId == -1)
{
auto bookmark = bookmarkModel.AddNew(
this->EditAuthor->Data(), this->EditTitle->Data(), this->EditUrl->Data());
Bookmarks->Append(ref new BookmarkViewModel(bookmark));
}
else
{
BookmarkViewModel^ bookmarkViewModel = (BookmarkViewModel^)Bookmarks->GetAt(editIndex);
bookmarkViewModel->Author = this->EditAuthor;
bookmarkViewModel->Title = this->EditTitle;
bookmarkViewModel->Url = this->EditUrl;
bookmarkModel.Update(
editId, bookmarkViewModel->Author->Data(),
bookmarkViewModel->Title->Data(), bookmarkViewModel->Url->Data());
}
editId = -1;
this->ShowListBox = "Visible";
}
There's nothing of great interest there except for the data-conversions. You'll end
up doing this all the time when you work in WinRT since you have to convert the
C++ types to the COM types (and in reverse). This is automated for you by .NET
when you work in C# or VB.
Note how I used a String
property to represent Visibility
(which is an enum
). This is to work around a bug in WinRT in the
dev preview where enum
s do not participate in data-binding. The bug
is known and has been reported.
Conclusion
Working on really early releases like the dev-preview can be frustrating but
it can also provide for some very interesting moments. It also gives you
opportunities to look under the covers a bit and discover how things actually
work behind the scenes. This can often help you make good design decisions or
choose appropriate coding patterns. Feel free to provide feedback and criticism, or ask questions through the
forum at the bottom of this article.
History
- Article first published : October 18, 2011