65.9K
CodeProject 正在变化。 阅读更多。
Home

Visual C++ 和 WinRT/Metro - 数据绑定基础

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (16投票s)

2011 年 10 月 18 日

CPOL

8分钟阅读

viewsIcon

102015

downloadIcon

514

使用 Visual C++ 进行 WinRT 数据绑定的介绍

开发者预览警告

本文及其内容基于 Windows 8 和 Visual Studio 11 的第一个公开开发者预览版。因此,本文中提到的代码片段和其他信息在操作系统/Visual Studio 正式发布前可能会发生重大更改。

引言

Metro/WinRT 通过 XAML 支持强大的数据绑定功能(您也可以完全通过代码实现,但 XAML 是推荐的方法)。WinRT 中的数据绑定支持在实现方式上与 WPF/Silverlight 不同,尽管从外部使用 C# 或 VB 时并不那么明显。这是因为 .NET 互操作代码实现了所需的封送/管道操作,以便从标准 .NET 代码平滑过渡到 WinRT 数据绑定。然而,在开发者预览版中,C++ 并非如此。C++ 开发人员需要实现某些接口才能使他们的类参与数据绑定。他们可能会在未来的版本中对此进行更改。因此,本文主要具有学术参考价值。

您期望它是如何工作的

以下是一些最小化的 C# 代码,展示了如何将 TextBlock 控件的 Text 属性绑定到 String 属性。

partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();

        this.DataContext = new Data();
    }
}

class Data
{
    public string Title
    {
        get
        {
            return "data binding!";
        }
    }
}

以下是 XAML。

<Grid x:Name="LayoutRoot" Background="#FF0C0C0C">
        <TextBlock Text="{Binding Title}"></TextBlock>
</Grid>

Data 是一个普通的 C# 类,Title 是一个标准的 C# 属性。无需额外工作即可使该类可数据绑定。我曾期望在使用 C++ 时获得类似的体验(除了语法上的变化)。可以肯定地说,我有点失望地发现它并非那么直接。同样,我希望这在未来的版本中会变得更简单。在此之前,如果您想尝试数据绑定或了解其工作原理,则需要为您的类添加一些额外的代码。

WinRT 数据绑定

WinRT 使用基于 COM 的数据绑定,任何可数据绑定的代码都应实现 ICustomProperty 接口。

MIDL_INTERFACE("30DA92C0-23E8-42A0-AE7C-734A0E5D2782")
ICustomProperty : public IInspectable
{
public:
    virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Type( 
        /* [out][retval] */ __RPC__out Windows::UI::Xaml::Interop::TypeName *value) = 0;
    
    virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Name( 
        /* [out][retval] */ __RPC__deref_out_opt HSTRING *value) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetValue( 
        /* [in] */ __RPC__in_opt IInspectable *obj,
        /* [out][retval] */ __RPC__deref_out_opt IInspectable **value) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE SetValue( 
        /* [in] */ __RPC__in_opt IInspectable *obj,
        /* [in] */ __RPC__in_opt IInspectable *value) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetIndexedValue( 
        /* [in] */ __RPC__in_opt IInspectable *obj,
        /* [in] */ __RPC__in_opt IInspectable *index,
        /* [out][retval] */ __RPC__deref_out_opt IInspectable **value) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE SetIndexedValue( 
        /* [in] */ __RPC__in_opt IInspectable *obj,
        /* [in] */ __RPC__in_opt IInspectable *value,
        /* [in] */ __RPC__in_opt IInspectable *index) = 0;
    
    virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_CanWrite( 
        /* [out][retval] */ __RPC__out boolean *value) = 0;
    
    virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_CanRead( 
        /* [out][retval] */ __RPC__out boolean *value) = 0;
    
};

以下是该接口的一个更简单的 .NET 式版本(更容易理解)。

[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; }
}

理论上,您所有可数据绑定的类都需要此接口。实际上,如果必须这样做,那将是非常不切实际和荒谬的。因此,一个好的开始是声明一个实现此接口的类。

实现一些用于数据绑定的基类和辅助类

我研究了 .NET 的实现,发现它们在实现中使用了 PropertyInfo(.NET 反射类,代表属性)。由于 WinRT 没有等效的类,我认为我应该首先编写这个。我选择了相同的名称,因为我本人可能有点缺乏想象力。

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);
    }

};

该类基本封装了一个属性,并允许调用者获取或设置值。为此,它使用了委托(在上面声明)。为了简单起见(也因为我很懒),我不考虑索引属性(在 XAML 中并不常用)。既然我们现在有了 PropertyInfo 类,实现 ICustomProperty 类就很容易了。

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();
  }
};

再次注意,不支持索引属性。现在我们有了 ICustomProperty 的实现,我们还需要 ICustomPropertyProvider 的实现。事实上,使对象可数据绑定的最简单方法是让它实现此接口。

MIDL_INTERFACE("7C925755-3E48-42B4-8677-76372267033F")
ICustomPropertyProvider : public IInspectable
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetCustomProperty( 
        /* [in] */ __RPC__in HSTRING name,
        /* [out][retval] */ __RPC__deref_out_opt Windows::UI::Xaml::Data::ICustomProperty **property) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetIndexedProperty( 
        /* [in] */ __RPC__in HSTRING name,
        /* [in] */ Windows::UI::Xaml::Interop::TypeName type,
        /* [out][retval] */ __RPC__deref_out_opt Windows::UI::Xaml::Data::ICustomProperty **property) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE GetStringRepresentation( 
        /* [out][retval] */ __RPC__deref_out_opt HSTRING *stringRepresentation) = 0;
    
    virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_Type( 
        /* [out][retval] */ __RPC__out Windows::UI::Xaml::Interop::TypeName *value) = 0;
    
};

最少,您需要实现 GetCustomProperty Type 属性。您可以将其他方法留空(或者至少对于本文中演示的任何内容都可以)。

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));
    }
  }
};

我决定将这个类作为我所有 WinRT 业务对象(将参与数据绑定)的基类。因此,我还实现了 IDisposable (编译器生成)和 INotifyPropertyChanged。请注意,INotifyPropertyChanged 是 Silverlight、WPF 和 Windows Forms 使用的同名接口的 COM 版本。它也起着相同的目的 - 通知侦听器属性的变化。

GetCustomProperty 方法返回一个合适的 ICustomProperty 对象来表示特定属性。属性在此处不添加(毕竟这是一个基类),而是由派生类添加。为了方便派生类,提供了一个 AddCustomProperty 方法供它们使用。有一个虚函数 AddCustomProperties 方法,派生类必须实现它,可以在其中添加任何数据绑定的属性。

这里一个相当令人讨厌的事情是,您不能直接将属性的 getter 或 setter 传递给委托。您必须将一个方法包装起来作为 getter,另一个作为 setter,然后将其用于委托。对于任何比较重要的业务对象来说,这可能有点可笑。为了避免这种情况,我编写了两个宏,它们使用 lambda 表达式来生成所需的委托。在我所做的所有事情中,这让我笑了,因为我将宏(C/C++ 数十年的特性)与 lambda 表达式(现代 C++ 特性)结合起来以提高简洁性。

#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;})

数据绑定现在可以工作了!

这是一个非常简单的 C++ 业务对象,绑定到一个 Xaml 视图。

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^));
  }

以下是 XAML。

<TextBlock x:Name="title" FontSize="24" TextAlignment="Left" Text="{Binding Title}" />

这个管用吗?WinRT,现在谁是你的爸爸? :-)

示例应用程序

我为演示项目使用了 MVVM 模式(广义上说)。模型由一个 C++ 类组成,该类被公开为 std::list,视图模型是一个 WinRT 包装器(支持数据绑定),视图全部是 XAML。模型数据在演示中不保存在磁盘上。在实际场景中,您可能会从遗留的 C++ 库、SQL Server、JSON Web 服务甚至纯文本数据文件中获取数据。我将通过一些代码片段来展示一些典型的 C++ 到 WinRT 的类型转换,并快速概述 C++ 数据如何通过 WinRT 中间层绑定到 XAML 视图。以下是 C++ 模型类(实际上是一个 struct)的样子。

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;
  }
};

请注意,数据属性的类型为 std::wstring(WinRT/COM 不识别的类型)。以下是一个由视图模型使用的管理器类。

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;
  }
};

管理器类拥有所有 CRUD 操作。注意那里使用了 lambda 表达式,使得代码与等效的 C# 代码非常相似。这里的模型类完全独立于 WinRT。我假设对于绝大多数外部数据源来说,情况都是如此。

视图是一个模板化的 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>

请注意 XAML 中定义的所有数据绑定。如果您以前从未使用过 XAML,您需要一段时间才能习惯以这种方式定义 UI。如果您做过 WPF 或 Silverlight,会容易得多。编辑器面板非常相似。

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

需要注意的一点是,这里的绑定是双向的。这是因为我也希望 UI 更改能够传播回业务对象。您可能已经注意到了其中的所有命令绑定。为了实现这一点,我使用了一个非常简单的 ICommand 实现。任何使用过 MVVM 形式的人都会认出它是一个标准的命令类,具有 execute 和 can-execute 方法。

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);
  }
};

连接视图模型

除了定义实际的属性和命令之外,最重要的事情是将其添加到属性链中(这会启用这些属性的数据绑定)。我曾花费许多愚蠢的时刻思考到底哪里出错了,直到我意识到我忘记为新添加的属性添加一个条目。

  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^));
  }

这是我个人希望他们在未来的版本中为我们自动化的重复乏味的部分。典型的数据属性如下所示。

property String^ EditAuthor
{
  String^ get()
  {
    return editAuthor;
  }

  void set(String^ value)
  {
    if(editAuthor != value)
    {
      editAuthor = value;
      FirePropertyChanged("EditAuthor");
    }
  }
}

这是常规的代码。除了对 FirePropertyChanged 的调用(这是告诉 WinRT 属性已更新所必需的)之外。命令也类似,只是需要将它们连接到正确的方法。

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));

其余代码中的重要部分主要涉及视图模型和模型之间的 WinRT C++ 交互。以下是一个数据读取的示例。

void LoadData()
{
  auto items = bookmarkModel.GetBookmarks();
  for(auto it = items.begin(); it != items.end(); it++)
  {
    Bookmarks->Append(ref new BookmarkViewModel(*it));
  }
}

BookmarkViewModel 是表示 C++ Bookmark 对象的视图模型可数据绑定包装器。

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^));
  }
};

构造函数根据需要进行类型转换,并且我们还连接了将参与数据绑定的自定义属性。以下是删除命令的实现。

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(...)
    {
    }
  }
}

忽略那里的 try-catch(这是为了绕过开发者预览版中的一个 bug)。请注意,在 WinRT 视图模型和 C++ 模型之间会进行单独的删除调用。

EditAdd 的工作原理类似。它们都打开编辑器面板,并将实际的保存任务委托给 Save 方法。

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";
} 

那里没有什么特别令人感兴趣的,除了数据转换。当您在 WinRT 中工作时,您将不得不一直这样做,因为您需要将 C++ 类型转换为 COM 类型(反之亦然)。当您使用 C# 或 VB 工作时,.NET 会为您自动完成此操作。

请注意,我使用 String 属性来表示 Visibility(这是一个 enum)。这是为了绕过开发者预览版中 WinRT 的一个 bug,该 bug 导致 enum 无法参与数据绑定。该 bug 已知并已报告。

结论

在开发者预览版等非常早期的版本上工作可能会令人沮丧,但它也可能带来一些非常有趣的时刻。它还为您提供了机会深入了解底层,并发现事物在幕后是如何实际工作的。这通常可以帮助您做出良好的设计决策或选择适当的编码模式。欢迎您通过本文底部的论坛提供反馈和批评,或提问。

历史

  • 文章首次发布时间:2011 年 10 月 18 日
© . All rights reserved.