Binding Silverlight UserControl custom properties to its' elements
Asked Answered
C

6

17

I'm trying to make a simple crossword puzzle game in Silverlight 2.0. I'm working on a UserControl-ish component that represents a square in the puzzle. I'm having trouble with binding up my UserControl's properties with its' elements. I've finally (sort of) got it working (may be helpful to some - it took me a few long hours), but wanted to make it more 'elegant'.

I've imagined it should have a compartment for the content and a label (in the upper right corner) that optionally contains its' number. The content control probably be a TextBox, while label control could be a TextBlock. So I created a UserControl with this basic structure (the values are hardcoded at this stage):

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

I've also created DependencyProperties in the Square class like this:

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

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

Now I'd like to figure out how to bind the Label and Content element to the two properties. I do it like this (in the code-behind file):

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

That would be more elegant done in XAML. Does anyone know how that's done?

Coauthor answered 8/4, 2009 at 12:15 Comment(1)
Such an important question yet such an elusive answer.Valve
G
19

First, set the DataContext on the UserControl using {RelativeSource Self}:

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

Now you can bind the individual elements to the properties of the usercontrol:

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

For SL 2.0, you'll need to set the DataContext on the UserControl's Loaded event handler.

private void UserControl_Loaded( object sender, RoutedEventArgs e ) {
    LayoutRoot.DataContext = this;
}
Gelatinate answered 26/4, 2010 at 8:22 Comment(2)
Isn't this just the opposite way of the MVVM good practices ? In the MVVM world, the DataContext of the UserControl should be set to a ViewModel. What if I want to bind a control to the ViewModel, and another one to a property of my UserControl ?Achieve
I agree with the above comment - unless I'm missing something this destroys the usefulness of the datacontext by assuming that everything you bind to in the component will be in the control. Aside from the trick of binding to the LayoutRoot parent I don't see any reasonable way to do this in xaml and so I'm resorting to just giving the elements a name and initializing them in my codebehind from the dependency property.Enwrap
B
7

As Silverlight cannot use FindAncestor technique you can use a trick similar to the one that sets the UserControl's name, but without breaking its functionality by using the name of the 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>

It worked in SL3 without having to add any additional code (I'm using it in a WP7 app), but don't know if you can use it in SL2. Well, I realize now how this question is old, hope it's still helpful, I've arrived here because the answers I got for the same problem in WP7 didn't convince me.

Blankbook answered 23/4, 2011 at 17:58 Comment(1)
Of the solutions on this page this seems like the only valid one, however it's awkward enough that I'm going to just initialize my stuff in the codebehind by name.Enwrap
T
2

I think you are looking for UI Element to Element Binding which is a feature of Silverlight 3.

Tithe answered 22/4, 2009 at 22:38 Comment(1)
As James Cadd said: This breaks if you consume the UserControl in the application and give it a name.Charnel
D
1

I may not be understanding your issue exactly. In Silverlight, you are able to bind to almost any data object. So, if you have a PuzzleSquare class that contains properties Content and Label, you may bind to these properties directly from the object.

Let's say you created a simple object 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;
      }    
    }

So, if you are building the app with the classic view/code behind model, your code behind would add this object to the DataContext property of the grid on page load:

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

Your Xaml would bind to the Square property:

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

Does that make sense?

ib.

Differentiate answered 8/4, 2009 at 17:48 Comment(2)
That makes sense, but my scenario is different. In your case PuzzleSquare and the UI are seperate classes. I'm not that far yet. I'm still defining my UI class. I want to add a public property to my UI class what binds to a sub-element property. Then I'll bind the data to the public prop of the UI.Fahy
Ran out of chars there.. So in my design, I'll be using my class like so <s:SomeCustPnl DataContext={StaticResource someDataInstance}> <DataTemplate> <s:Square Content="{Binding contentProp}"/> .. However, I need to wire up the Content property to a subelement of Square. Does that make sense?Fahy
L
0

This worked in Silverlight 4.0

Put a name on the UserControl, and then refer to it in the TextBlock

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

        <TextBlock x:Name="Label" ...
            Text="{Binding Path=LabelText,ElementName=Square}"/>
Luisluisa answered 9/7, 2010 at 11:9 Comment(1)
This breaks if you consume the UserControl in the application and give it a name.Modulation
B
0

Try this:

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

I can bind in both XAML and code behind.

Bankable answered 27/9, 2011 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.