将Silverlight UserControl的自定义属性绑定到其元素上

17
我正在尝试在Silverlight 2.0中制作一个简单的填字游戏。我正在开发一个类似UserControl的组件,用于表示拼图中的方格。我在将UserControl的属性与其元素绑定时遇到了麻烦。最终我(有点)解决了这个问题(对一些人可能有帮助——我花了几个小时),但是想要让它更加“优雅”。
我想象它应该有一个内容区和一个标签(位于右上角),可选地包含其编号。内容控件可能是TextBox,而标签控件可以是TextBlock。所以我创建了一个具有此基本结构的UserControl(值在此阶段为硬编码):
    <UserControl x:Class="XWord.Square"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    FontSize="30" 
    Width="100" Height="100">
        <Grid x:Name="LayoutRoot" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1" 
                Text="7"/>
            <TextBox x:Name="Content" Grid.Row="1" Grid.Column="0"  
                Text="A"
                BorderThickness="0" />

        </Grid>
    </UserControl>

我还在Square类中创建了这样的DependencyProperties:

     public static readonly DependencyProperty LabelTextProperty;
     public static readonly DependencyProperty ContentCharacterProperty;

     // ...(static constructor with property registration, .NET properties
     // omitted for brevity)...

我现在想弄清楚如何将Label和Content元素绑定到这两个属性。我是这样做的(在代码后台文件中):

     Label.SetBinding( TextBlock.TextProperty, new Binding { Source = this, Path = new PropertyPath( "LabelText" ), Mode = BindingMode.OneWay } );
     Content.SetBinding( TextBox.TextProperty, new Binding { Source = this, Path = new PropertyPath( "ContentCharacter" ), Mode = BindingMode.TwoWay } );

这个最好在XAML中完成,有没有人知道怎么做?


3
如此重要的问题却如此难以回答。 - Scott Willeke
6个回答

19

首先,使用{RelativeSource Self}在UserControl上设置DataContext:

<UserControl x:Class="XWord.Square"  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
FontSize="30"   
Width="100" Height="100" 
DataContext="{Binding RelativeSource={RelativeSource Self}}">

现在您可以将个别元素绑定到用户控件的属性:

<TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1" 
Text="{Binding LabelText}"/>  
<TextBox x:Name="Content" Grid.Row="1" Grid.Column="0" 
Text="{Binding ContentCharacter}" BorderThickness="0" />

对于 SL 2.0,您需要在UserControl的Loaded事件处理程序上设置DataContext。

private void UserControl_Loaded( object sender, RoutedEventArgs e ) {
    LayoutRoot.DataContext = this;
}

这不是 MVVM 最佳实践的相反方式吗?在 MVVM 的世界中,UserControl 的 DataContext 应该设置为 ViewModel。如果我想将一个控件绑定到 ViewModel,另一个控件绑定到我的 UserControl 的属性,怎么办? - JYL
我同意上面的评论 - 除非我漏掉了什么,否则这将通过假设您绑定到组件中的所有内容都在控件中来破坏数据上下文的有用性。除了绑定到LayoutRoot父级的技巧外,我不认为在XAML中有任何合理的方法来做到这一点,所以我只是给元素命名,并从依赖属性中初始化它们在我的代码后台。 - Pat Niemeyer

7
由于 Silverlight 无法使用 FindAncestor 技术,因此您可以使用类似于设置 UserControl 名称的技巧,但不会破坏其功能,而是使用 LayoutRoot 的名称...
<UserControl x:Class="XWord.Square"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FontSize="30" 
Width="100" Height="100">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBlock x:Name="{Binding Path=Parent.LabelText, ElementName=LayoutRoot}" Grid.Row="0" Grid.Column="1" 
            Text="7"/>
        <TextBox x:Name="{Binding Path=Parent.ContentCharacter, ElementName=LayoutRoot}" Grid.Row="1" Grid.Column="0"  
            Text="A"
            BorderThickness="0" />
    </Grid>
</UserControl>

在SL3中,无需添加任何额外的代码即可正常工作(我正在一个WP7应用程序中使用它),但我不知道是否可以在SL2中使用。好吧,我现在意识到这个问题很旧了,希望它仍然有用,我之所以来到这里是因为我对在WP7中解决同样问题得到的答案并不满意。


这一页上的解决方案中,似乎只有这个是有效的,但它太笨拙了,我还是要通过名称在代码后台初始化我的东西。 - Pat Niemeyer

2

正如James Cadd所说:如果您在应用程序中使用UserControl并给它命名,那么这将会出现错误。 - Dänu

1

我可能没有完全理解您的问题。在Silverlight中,您可以绑定到几乎任何数据对象。因此,如果您有一个包含Content和Label属性的PuzzleSquare类,您可以直接从该对象绑定到这些属性。

假设您创建了一个简单的PuzzleSquare对象:

    public class PuzzleSquare
    {
      public string Content{ get; set; }
      public string Label{ get; set; }

      public void PuzzleSquare(){};
      public void PuzzleSquare(string label, string content):this()
      {
         Content = content;
         Label = label;
      }    
    }

因此,如果您正在使用经典的视图/代码后模型构建应用程序,则代码后将在页面加载时将此对象添加到网格的DataContext属性中:

LayoutRoot.DataContext = new PuzzleSquare("1", "A");

你的Xaml将绑定到Square属性:

    <TextBlock x:Name="Label" Grid.Row="0" Grid.Column="1" 
Text="{Binding Label}"/>            
    <TextBox x:Name="Content" Grid.Row="1" Grid.Column="0" 
Text="{Binding Content}" BorderThickness="0" />

这有意义吗?

同上。


1
这很有道理,但是我的情况不同。在您的情况下,PuzzleSquare和UI是单独的类。我还没有到那一步。我还在定义我的UI类。我想向我的UI类添加一个公共属性,该属性绑定到子元素属性。然后我将把数据绑定到UI的公共属性。 - Ronald Zarīts
字符用完了。所以在我的设计中,我将使用我的类,如下所示: <s:SomeCustPnl DataContext={StaticResource someDataInstance}> <DataTemplate> <s:Square Content="{Binding contentProp}"/> ..但是,我需要将Content属性连接到Square的子元素。这有意义吗? - Ronald Zarīts

0

这在Silverlight 4.0中有效

给UserControl命名,然后在TextBlock中引用它

 <UserControl x:Class="XWord.Square"
    ...omitted for brevity ...
    x:Name="Square">

        <TextBlock x:Name="Label" ...
            Text="{Binding Path=LabelText,ElementName=Square}"/>

1
如果在应用程序中使用并为其命名UserControl,则会导致此代码出现错误。 - James Cadd

0

试试这个:

Public ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(ButtonEdit), New System.Windows.PropertyMetadata("", AddressOf TextPropertyChanged))
Public Property Text As String
    Get
        Return GetValue(TextProperty)
    End Get
    Set(ByVal value As String)
        SetValue(TextProperty, value)
    End Set
End Property
Private Sub TextPropertyChanged()
    If String.IsNullOrEmpty(Text) Then
        TextBox1.Text = ""
    Else
        TextBox1.Text = Text
    End If
End Sub
Private Sub TextBox1_LostFocus(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles TextBox1.LostFocus
    Text = TextBox1.Text
End Sub

我可以在XAML和代码后台中进行绑定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接