none
How to make a template with two Labels within

    Question

  • I'm a WPF newbie trying to figure out how to make some sort of XAML only template that will encapsulate the majority of functionality required in order to define two Labels, one which receives its content when the item is declared and the other's is bound to an external propertry.

    If someone would be so kind as to jot off a quick little thing to get me started I'd be ever so grateful.

    As an example of what I'm trying to accomplish, here's what I've got so far:

    <Window x:Class="pocDualLabelTemplate.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        
        <Window.Resources>
            
            <ControlTemplate x:Key="dl" TargetType="ContentControl">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
     
                    <Border Grid.Column="0" Grid.ColumnSpan="2"
                            CornerRadius="10" BorderBrush="LightGray" BorderThickness="1"
                            Margin="5">
                    </Border>
                    
                    <Label 
                        Grid.Column="0"
                        HorizontalContentAlignment="Right" VerticalContentAlignment="Center">
                        From xaml
                    </Label>
                    
                    <Label 
                        Grid.Column="1"
                        VerticalContentAlignment="Center">
                        Content from binding
                    </Label>
                </Grid>
            </ControlTemplate>
            
        </Window.Resources>
        
        <Grid>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
     
            <Label Grid.Column="0" Grid.Row="0" Template="{StaticResource dl}"  />
            <Label Grid.Column="1" Grid.Row="0" Template="{StaticResource dl}" />
        </Grid>
    </Window>
    

    At the time that <Label /> is defined, I want to define the content of the left Label and specify the binding of the right. How can this be done?


    Richard Lewis Haggard


    Tuesday, October 02, 2012 8:22 PM

Answers

  • Hello Richard.

    Have you tried to just do it with a style and make use of the Tag as I think Malc suggested?

    Something like this...

    <Style x:Key="LabelStyleTwoContent" TargetType="{x:Type Label}">
    			<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    			<Setter Property="Background" Value="Transparent"/>
    			<Setter Property="Padding" Value="5"/>
    			<Setter Property="HorizontalContentAlignment" Value="Left"/>
    			<Setter Property="VerticalContentAlignment" Value="Top"/>
    			<Setter Property="Template">
    				<Setter.Value>
    					<ControlTemplate TargetType="{x:Type Label}">
    						<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
    							<StackPanel Orientation="Horizontal">
    								<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
    								<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="5,0,0,0" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=Tag}"/>
    							</StackPanel>
    						</Border>
    						<ControlTemplate.Triggers>
    							<Trigger Property="IsEnabled" Value="false">
    								<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
    							</Trigger>
    						</ControlTemplate.Triggers>
    					</ControlTemplate>
    				</Setter.Value>
    			</Setter>
    		</Style>

    And then your basic xaml would be pretty simple...

    <Label Content="Label 1" Tag="Label 2" VerticalAlignment="Top" Style="{DynamicResource LabelStyleTwoContent}"/>
    ~Christine


    My Gallery

    Thursday, October 04, 2012 4:50 PM

All replies

  • I use user controls a lot as templates to contain other controls.  As for the bindings you will most likely want to use DependencyProperties because you can wire up bindings at design time in VS2010/12 only when you implement dependencyproperties.

    There are plenty of other ways, one of them is to create what is called a custom control, one of the best tutorials I've seen out there is here.  When you create a custom control you are not incurring any additional overhead other than the XAML markup and the code that supports it.  The key for this to work is to ensure that you register the DefaultKeyStyle property.  This is how the WPF system knows to couple the code to the XAML.  It can be tough to get this at first because you have to create the proper directories for the Generic.XAML AND you cannot see the XAML in designer when you are designing the control.  So what I do is just add a user control to that folder and do all the design there.  When I like what I see I copy it to the Generic.XAML code.

    Another way is to create a CS class that inherits from the parent control you want (for example a Stack Panel).  Then you can create a code only solution which does what you want.  I've used this successfully many times before in this exact scenario you mention... (I want a zillion controls, but I don't discover their name or content until run time).  When you do it this way you have to wire up event handlers in order for anything activity to occur using code.  Make sure to name each control so you can get a clue who is calling!  One other trick is to use the control property named TAG to store other pertinent information to help you figure out what to do. One way I use the TAG property is to store URL information for a button.  When the button is clicked I just read the TAG string to get URL.  Works perfect!


    JP Cowboy Coders Unite!


    Tuesday, October 02, 2012 10:34 PM
  • Hi,

    Something like this?

    <Page
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:sys="clr-namespace:System;assembly=mscorlib">
      <Grid> 
      <Grid.Resources> 
      
       <x:Array x:Key="ValuesList" Type="sys:String" >
       <sys:String>Hello</sys:String>
       <sys:String>World</sys:String>
       </x:Array>
      
    <Style x:Key="BorderContentStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Height" Value="32"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentControl">
                        <Grid>
                        <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="125"/>
                        </Grid.ColumnDefinitions>
                        <Label  BorderBrush="Black" BorderThickness="1,1,0,1" Background="#CBBBDDFC" Content="Right aligned constant" FontWeight="SemiBold" HorizontalContentAlignment="Right" Width="175"/>
                        <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1">
                        <ContentPresenter Margin="5,5" />
                        </Border>
                        </Grid>
                </ControlTemplate>
            </Setter.Value>
         </Setter>
    </Style>
    
    
      </Grid.Resources>  
    
    
                            <ListView 
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      IsSynchronizedWithCurrentItem="True"
                                      ItemContainerStyle="{StaticResource BorderContentStyle}"
                                      ItemsSource="{Binding Source={StaticResource ValuesList}}"                                  
                                     />
           </Grid>
    </Page>

    Of course the items don't have to be in a ListView. In which case just something like this:

      <ContentControl Style="{StaticResource BorderContentStyle}" Content="Test"/>

    M



    • Edited by Malc63 Tuesday, October 02, 2012 10:57 PM
    Tuesday, October 02, 2012 10:47 PM
  • Thanks, but I'm trying to avoid a custom control. This is such a lightweight requirement that adding the combination of cs and xaml and everything else is more trouble than it is worth. Besides, I know how to do custom controls and I am clueless on this template thing.

    Richard Lewis Haggard

    Tuesday, October 02, 2012 11:26 PM
  • Excellent. This is on the road to where I want to go. The next step is to define a means whereby two separate contents can be specified, one that is a simple string of some sort and the other is a binding to an external object's property. In this case what I want to do is contruct an element that accepts a caption string and a binding that will fill the other content.

    Richard Lewis Haggard

    Tuesday, October 02, 2012 11:30 PM
  • Hi,

    Well the content is easy enough, as that's just simple binding . As for the caption string if you set it from code you can refer to the captionLabel via it's name. Please check this code:

    CaptionTwinsTest

    M


    • Edited by Malc63 Wednesday, October 03, 2012 12:11 AM
    Wednesday, October 03, 2012 12:02 AM
  • Hi,

    Well that's one way to do it, but I didn't like it, so I managed to come up with this:

    <Grid>
            <Grid.Resources>
    
                <Style x:Key="BorderContentStyle" TargetType="{x:Type ContentControl}">
                    <Setter Property="Height" Value="32"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="ContentControl">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="125"/>
                                    </Grid.ColumnDefinitions>
                                    <Label BorderBrush="Black" BorderThickness="1,1,0,1" FontWeight="SemiBold" HorizontalContentAlignment="Right" Width="175"
                                           Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=Tag}"/>
                                    <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1">
                                        <ContentPresenter Margin="5,5" />
                                    </Border>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
                
                <sys:String x:Key="aCaption" >Caption value</sys:String>
                <sys:String x:Key="aString" >String value</sys:String>
    
            </Grid.Resources>
        <ContentControl Style="{StaticResource BorderContentStyle}" Tag="{Binding Source={StaticResource aCaption}}" Content="{Binding Source={StaticResource aString}}"/>
    
        </Grid>
    M



    Wednesday, October 03, 2012 12:35 AM
  • Oh yes and I forgot another way; which is what Malc showed, Styles.  However;  in the XAML markup above you will only get one control.  You will want to create a style for a itemcontainer like a datagrid or listbox instead.

    JP Cowboy Coders Unite!

     

    Wednesday, October 03, 2012 1:27 AM
  • If you do need it for a ListView/Box etc...

      <Grid> 
      <Grid.Resources>
      
       <col:SortedList x:Key="ValuesList">
        <sys:String x:Key="0">Bob</sys:String>
        <sys:String x:Key="1">Is</sys:String>
        <sys:String x:Key="2">Your</sys:String>
        <sys:String x:Key="3">Uncle</sys:String>
    </col:SortedList>
      
      <Style x:Key="BorderContentStyle" TargetType="{x:Type ContentControl}">
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="ContentControl">
                      <Grid>
                          <Grid.ColumnDefinitions>
                              <ColumnDefinition Width="50"/>
                              <ColumnDefinition Width="125"/>
                          </Grid.ColumnDefinitions>
                          <Label BorderBrush="Black" BorderThickness="1,1,0,1" Background="#FEFCFDBB" FontWeight="SemiBold" HorizontalContentAlignment="Right"
                                 Content="{Binding Path=Key}"/> <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1"> <ContentPresenter Margin="5,5" Content="{Binding Path=Value}" /> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </Grid.Resources>
                            <ListView
                                      HorizontalAlignment="Center"
                                      VerticalAlignment="Center"
                                      ItemContainerStyle="{StaticResource BorderContentStyle}"
                                      ItemsSource="{Binding Source={StaticResource ValuesList}}"                                  
                                     /> </Grid>
    M







    • Edited by Malc63 Wednesday, October 03, 2012 2:22 AM
    Wednesday, October 03, 2012 2:06 AM
  • Very interesting but I'm not sure how it would work for me. Let me restate - what I'm looking for is a technique that will allow me to create an entity whose primary function is to display two strings which can then be used some 50 times or so to display various properties from a model object. I want to use it multiple times like this:

    <Label Grid.Column="x" Grid.Row="y" StaticResource="dl" Content1="Some string" Content2="{Binding Path=ToSomeProperty}" />

    As time goes on, I'll add various cosmetic decorations to it to make it pretty but the primary thing is how to specify the mechanism by which the two contents will get from the declaritive portion of the XAML to the control template, a matter upon which I am particularly clueless so if you more knowledgable persons can not only point me in the right direction but explain how it works, that would be great.


    Richard Lewis Haggard

    Wednesday, October 03, 2012 2:19 PM
  • Hi,

    Unless I'm missing something doesn't this do that?

                <sys:String x:Key="aCaption" >Caption value</sys:String>
                <sys:String x:Key="aString" >String value</sys:String>
    
            </Grid.Resources>
        <ContentControl Style="{StaticResource BorderContentStyle}" Tag="{Binding Source={StaticResource aCaption}}" Content="{Binding Source={StaticResource aString}}"/>

    You can bind/hardcode the Tag for your caption and the content for your value. As it is you can use these as you would a label. The last post was just to show you that you could use a similar technique to place the content in a list.

    M





    • Edited by Malc63 Wednesday, October 03, 2012 2:53 PM
    Wednesday, October 03, 2012 2:39 PM
  • So first Richard you have to decided on how you want to populate the 50 items.  Binding or via Code.  If you do it by binding you need an items container of some sort.  If you do it by code you can use anything you want to use.

    This works for Binding method:

        class myItems
        {
            public string Header { get; set; }
            public string Content { get; set; }
            public ObservableCollection<myItems> GetData()
            {
                ObservableCollection<myItems> oc = new ObservableCollection<myItems>();
                for (int i = 0; i < 100; i++)
                {
                    myItems current = new myItems();
                    current.Header = "Header : " + i;
                    current.Content = "Content :  " + i;
                    oc.Add(current);
    
                }
                return oc;
    
            }


    <Window x:Class="ControlTemplate.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my="clr-namespace:ControlTemplate.Models" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="modelViewSource" d:DesignSource="{d:DesignInstance my:Model, CreateList=True}" />
            <CollectionViewSource x:Key="modelocViewSource" Source="{Binding Path=oc, Source={StaticResource modelViewSource}}" />
        </Window.Resources>
        <Grid DataContext="{StaticResource modelocViewSource}">
            <DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Name="ocDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected">
                <DataGrid.Columns>
                    <DataGridTextColumn x:Name="contentColumn" Binding="{Binding Path=Content}" Header="Content" Width="SizeToHeader" />
                    <DataGridTextColumn x:Name="headerColumn" Binding="{Binding Path=Header}" Header="Header" Width="SizeToHeader" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>

    And finally code  behind:

                          
    publicpartialclassMainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();          
            }
     
            privatevoid Window_Loaded(object sender, RoutedEventArgs e)
            {
     
                System.Windows.Data.CollectionViewSource modelViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("modelViewSource")));
                // Load data by setting the CollectionViewSource.Source property:myItems stuff = newmyItems();
                modelViewSource.Source = stuff.GetData();
                ocDataGrid.DataContext = modelViewSource;
            }  
        }
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();          
            }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
    
                System.Windows.Data.CollectionViewSource modelViewSource = ((System.Windows.Data.CollectionViewSource)(this.FindResource("modelViewSource")));
                // Load data by setting the CollectionViewSource.Source property:
                myItems stuff = new myItems();
                modelViewSource.Source = stuff.GetData();
                ocDataGrid.DataContext = modelViewSource;
            }  
        }


    JP Cowboy Coders Unite!

    Wednesday, October 03, 2012 2:59 PM
  • I wrote this for personal use, but please feel free..

    public class AutoGrid : Grid
    {
        public static readonly DependencyProperty RowsProperty =
            DependencyProperty.Register("Rows", typeof (int), typeof (AutoGrid), new FrameworkPropertyMetadata(RowsChanged));
    
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof (int), typeof (AutoGrid), new FrameworkPropertyMetadata(ColumnsChanged));
    
        public static readonly DependencyProperty ColumnSpacingProperty =
            DependencyProperty.Register("ColumnSpacing", typeof (GridLength), typeof (AutoGrid), new FrameworkPropertyMetadata(ColumnSpacingChanged));
    
        public static readonly DependencyProperty FooterProperty =
            DependencyProperty.Register("Footer", typeof (FrameworkElement), typeof (AutoGrid), new FrameworkPropertyMetadata(FooterChanged));
    
        public static readonly DependencyProperty FooterHeightProperty =
            DependencyProperty.Register("FooterHeight", typeof (GridLength), typeof (AutoGrid), new FrameworkPropertyMetadata(FooterHeightChanged));
    
        public int Rows
        {
            get { return (int) GetValue(RowsProperty); }
            set { SetValue(RowsProperty, value); }
        }
    
        public int Columns
        {
            get { return (int) GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }
    
        public GridLength ColumnSpacing
        {
            get { return (GridLength) GetValue(ColumnSpacingProperty); }
            set { SetValue(ColumnSpacingProperty, value); }
        }
    
        public FrameworkElement Footer
        {
            get { return (FrameworkElement) GetValue(FooterProperty); }
            set { SetValue(FooterProperty, value); }
        }
    
        public GridLength FooterHeight
        {
            get { return (GridLength) GetValue(FooterHeightProperty); }
            set { SetValue(FooterHeightProperty, value); }
        }
    
        protected override Size MeasureOverride(Size constraint)
        {
            int initalColumn = 0;
            int currentRow = 0;
    
            for (int n = 0; n < Children.Count; n++)
            {
                if (currentRow >= Rows)
                {
                    initalColumn += 3;
                    currentRow = 0;
                }
    
                var element = Children[n] as FrameworkElement;
    
                UIElement label = Attached.GetLabel(element);
    
                if (Children.Cast<FrameworkElement>().Select(x => Attached.GetLabel(x)).Contains(element)) continue;
    
                if (element == Footer) continue;
    
                UIElement rowFooter = Attached.GetRowFooter(element);
                bool starSize = Attached.GetStarSize(element);
    
                if (starSize) RowDefinitions[currentRow].Height = new GridLength(1, GridUnitType.Star);
    
                if (Children.Cast<FrameworkElement>().Select(x => Attached.GetRowFooter(x)).Contains(element)) continue;
    
                SetRow(element, currentRow);
    
                if (label != null)
                {
                    SetColumn(element, initalColumn + 1);
    
                    if (!Children.Contains(label)) Children.Add(label);
    
                    SetColumn(label, initalColumn);
                    SetRow(label, currentRow);
                }
                else
                {
                    SetColumn(element, initalColumn);
                    SetColumnSpan(element, 2);
                }
    
                if (rowFooter != null)
                {
                    SetColumn(element, initalColumn + 1);
    
                    if (!Children.Contains(rowFooter)) Children.Add(rowFooter);
    
                    SetColumn(rowFooter, initalColumn + 2);
                    SetRow(rowFooter, currentRow);
                    RowDefinitions[currentRow].Height = FooterHeight;
                }
    
                currentRow += (int) element.GetValue(RowSpanProperty);
            }
    
            return base.MeasureOverride(constraint);
        }
    
        private static void RowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (AutoGrid) d;
    
            instance.RowDefinitions.Clear();
    
            for (int n = 0; n < instance.Rows; n++)
            {
                instance.RowDefinitions.Add(new RowDefinition {Height = new GridLength(1, GridUnitType.Auto)});
            }
        }
    
        private static void ColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (AutoGrid) d;
    
            instance.ColumnDefinitions.Clear();
    
            for (int n = 0; n < instance.Columns*3; n++)
            {
                if (n%3 == 0)
                {
                    instance.ColumnDefinitions.Add(new ColumnDefinition {Width = new GridLength(1, GridUnitType.Auto)});
                }
                else if (n%3 == 1)
                {
                    instance.ColumnDefinitions.Add(new ColumnDefinition {Width = new GridLength(1, GridUnitType.Star)});
                }
                else if (n%3 == 2)
                {
                    instance.ColumnDefinitions.Add(new ColumnDefinition {Width = instance.ColumnSpacing});
                }
            }
        }
    
        private static void ColumnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (AutoGrid) d;
    
            for (int n = 2; n < instance.Columns*3; n += 3)
            {
                instance.ColumnDefinitions[n].Width = instance.ColumnSpacing;
            }
        }
    
        private static void FooterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (AutoGrid) d;
    
            if (instance.RowDefinitions.Count == instance.Rows)
            {
                instance.RowDefinitions.Add(new RowDefinition {Height = instance.FooterHeight});
                instance.Children.Add(instance.Footer);
                SetRow(instance.Footer, instance.Rows);
                SetColumnSpan(instance.Footer, instance.Columns*3);
            }
        }
    
        private static void FooterHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var instance = (AutoGrid) d;
    
            if (instance.RowDefinitions.Count == instance.Rows + 1)
            {
                instance.RowDefinitions[instance.Rows].Height = instance.FooterHeight;
            }
        }
    }


    public static class Attached
    {
        public static readonly DependencyProperty StarSizeProperty =
            DependencyProperty.RegisterAttached("StarSize", typeof (bool), typeof (Attached), new PropertyMetadata(default(bool)));
    
        public static void SetStarSize(UIElement element, bool value)
        {
            element.SetValue(StarSizeProperty, value);
        }
    
        public static bool GetStarSize(UIElement element)
        {
            return (bool) element.GetValue(StarSizeProperty);
        }
    
        public static readonly DependencyProperty LabelProperty =
            DependencyProperty.RegisterAttached("Label", typeof (UIElement), typeof (Control), new PropertyMetadata(default(Control)));
    
        public static readonly DependencyProperty RowFooterProperty =
            DependencyProperty.RegisterAttached("RowFooter", typeof (UIElement), typeof (Control), new PropertyMetadata(default(UIElement)));
    
        public static void SetLabel(UIElement element, UIElement value)
        {
            element.SetValue(LabelProperty, value);            
        }
    
        public static UIElement GetLabel(UIElement element)
        {
            return (UIElement) element.GetValue(LabelProperty);
        }
    
        public static void SetRowFooter(UIElement element, UIElement value)
        {
            element.SetValue(RowFooterProperty, value);
        }
    
        public static UIElement GetRowFooter(UIElement element)
        {
            return (UIElement) element.GetValue(RowFooterProperty);
        }    
    }
    <Controls:AutoGrid Rows="1" Columns="1">
      <TextBlock Text="{Binding SomeValue}">
        <Controls:Attached.Label>
          <TextBlock Text="SomeLabel"/>
        </Controls:Attached.Label>
      </TextBlock>  
    </Controls:AutoGrid>

    Hope that helps,

    Matt


    Thursday, October 04, 2012 4:01 AM
  • Another really simple would be to derive from Grid, add two ColumnDefinitions in the constructor, expose a DP called Text and one called TextBlock. Add a TextBlock with the Text DP in the first ColumnDefinition, and another TextBlock in second ColumnDefinitions. XAML would end up

    <local:DualLabel Text="Label">
    
      <local:DualLabel.TextBlock>
    
        <TextBlock Text="{Binding SomeText}">
    
      </local:DualLabel.TextBlock>
    
    </local:DualLabel>
    

    If you don't care to style the TextBlock at all, you could expose a BindingExpression DP and have <local:DualLabel Text="Label" Binding="{Binding SomeText}"> instead.

    Warm regards,

    Matt


    Thursday, October 04, 2012 5:49 AM
  • Haha, cuz I love it :)

    public class DualLabel : Grid
    {
        private TextBlock _labelTextBlock = new TextBlock();
        private TextBlock _valueTextBlock = new TextBlock();
    
        public DualLabel()
        {
            this.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength() });
            this.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength() });
    
            Grid.SetColumn(LabelTextBlock, 0);
            Grid.SetColumn(ValueTextBlock, 1);
    
            this.Children.Add(LabelTextBlock);
            this.Children.Add(ValueTextBlock);
        }
    
        public string LabelText
        {
            get { return ( string)GetValue(LabelTextProperty); }
            set { SetValue(LabelTextProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for LabelText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LabelTextProperty =
            DependencyProperty.Register("LabelText", typeof( string), typeof(DualLabel), new FrameworkPropertyMetadata(new PropertyChangedCallback(LabelTextChanged)) );
    
        private static void LabelTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DualLabel dualLabel = (DualLabel) d;
    
            dualLabel.LabelTextBlock.Text = (string)e.NewValue;
        }
    
    
        public BindingExpression ValueBinding
        {
            get { return (BindingExpression)GetValue(ValueBindingProperty); }
            set { SetValue(ValueBindingProperty, value); }
        }
    
        public TextBlock LabelTextBlock
        {
            get { return _labelTextBlock; }
            set { _labelTextBlock = value; }
        }
    
        public TextBlock ValueTextBlock
        {
            get { return _valueTextBlock; }
            set { _valueTextBlock = value; }
        }
    
        // Using a DependencyProperty as the backing store for ValueBinding.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueBindingProperty =
            DependencyProperty.Register("ValueBinding", typeof(BindingExpression), typeof(DualLabel), new FrameworkPropertyMetadata(new PropertyChangedCallback(ValueBindingChanged)));
    
        private static void ValueBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DualLabel dualLabel = (DualLabel)d;
    
            BindingExpression binding = (BindingExpression)e.NewValue;
    
            dualLabel.ValueTextBlock.SetBinding(TextBlock.TextProperty, binding.ParentBindingBase);
        }
    }


    <WpfApplication309:DualLabel LabelText="LabelText" ValueBinding="{Binding TestText}"></WpfApplication309:DualLabel>

    You could sex this up a bit by exposing margins, font, fontsize etc as DP's.

    Warm regards,

    Matt


    Thursday, October 04, 2012 6:11 AM
  • With all due respect to Matt, I believe the solution I provided is more simple.  Note that if you don't like the format of the data grid, you have full control in changing the template to suit your needs.  

    JP Cowboy Coders Unite!

    Thursday, October 04, 2012 1:28 PM
  • So many nice ideas. I appreciate the input.

    Is there no way this can be done entirely in XAML? If possible, I'd rather avoid code behind. I would have thought that this would have been an easy one to do in XAMl only but that's only because i don't know how to do it and, as we all know, nothing is impossible to a person who is sufficiently ignorant of the facts.

    I'll restate the goal since we seem to be spending a lot of time talking about things that do not appear to serve that goal: create a XAML only entity in the resource that displays two strings, the intent being that each of these entities displays a caption and data. These entities will then be used many times, each time they will have their contents and placement specified. In this particular case the caption will be specified each time one of these things is implemented and the data will come as a result o being bound to an external object's property but these are implementation details.

    My idea was to embed two Labels within a grid but I have been unable to figure out how to pipe the data to be displayed to the individual Labels within the construct.

    As an example of what I was thinking (with much left out because I'm typing this freehand and am lazy)

    <ControlTemplate x:Key="dl" TargetType="ContentControl">
        <Grid>
            <Label Grid.Column="0" Content="{MagicIncantation1}" />
            <Label Grid.Column="1" Content="{MagicIncantation2}"/>
        </Grid>
    </ContentTemplate>

    And then used sort of like this:

    <Grid>
        <Label Template="StaticResource dl}" Grid.Row="0" MagicIncantation1="Pulse Width:" MagicIncantation2="{Binding Path=PulseWidth}" />
        <Label Template="StaticResource dl}" Grid.Row="1" MagicIncantation1="Power:" MagicIncantation2="{Binding Path=Power}" />
    </Grid>

    Is this sort of thing possible or am I barking up the wrong tree?


    Richard Lewis Haggard

    Thursday, October 04, 2012 3:54 PM
  • Hello Richard.

    Have you tried to just do it with a style and make use of the Tag as I think Malc suggested?

    Something like this...

    <Style x:Key="LabelStyleTwoContent" TargetType="{x:Type Label}">
    			<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    			<Setter Property="Background" Value="Transparent"/>
    			<Setter Property="Padding" Value="5"/>
    			<Setter Property="HorizontalContentAlignment" Value="Left"/>
    			<Setter Property="VerticalContentAlignment" Value="Top"/>
    			<Setter Property="Template">
    				<Setter.Value>
    					<ControlTemplate TargetType="{x:Type Label}">
    						<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
    							<StackPanel Orientation="Horizontal">
    								<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
    								<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="5,0,0,0" Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}, Path=Tag}"/>
    							</StackPanel>
    						</Border>
    						<ControlTemplate.Triggers>
    							<Trigger Property="IsEnabled" Value="false">
    								<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
    							</Trigger>
    						</ControlTemplate.Triggers>
    					</ControlTemplate>
    				</Setter.Value>
    			</Setter>
    		</Style>

    And then your basic xaml would be pretty simple...

    <Label Content="Label 1" Tag="Label 2" VerticalAlignment="Top" Style="{DynamicResource LabelStyleTwoContent}"/>
    ~Christine


    My Gallery

    Thursday, October 04, 2012 4:50 PM
  • Excellent. I was able to take your suggestion and make it work although I don't actually understand the details of what and how.

    Here's my skeleton sample code:

    <Window x:Class="pocDualLabelTemplate.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="Two Content Labels" Height="141" Width="525">
        
        <Window.Resources>
            <Style x:Key="L2" TargetType="{x:Type Label}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type Label}">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition />
                                        <ColumnDefinition />
                                    </Grid.ColumnDefinitions>
     
                                <ContentPresenter Grid.Column="0" Margin="5" 
                                                  HorizontalAlignment="Right" VerticalAlignment="Center"
                                                  Content="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Tag}" />
                                <ContentPresenter Grid.Column="1" Margin="5" 
                                                  HorizontalAlignment="Left" VerticalAlignment="Center"/>
                                    
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
        </Window.Resources>
        
        <Grid>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
     
            <Label Grid.Column="0" Grid.Row="0" Style="{DynamicResource L2}" Tag="Caption1:" Content="Content1"/>
            <Label Grid.Column="1" Grid.Row="0" Style="{DynamicResource L2}" Tag="Caption2:" Content="Content2"/>
            <Label Grid.Column="0" Grid.Row="1" Style="{DynamicResource L2}" Tag="Caption3:" Content="Content3"/>
            <Label Grid.Column="1" Grid.Row="1" Style="{DynamicResource L2}" Tag="Caption4:" Content="Content4"/>
        </Grid>
    </Window>
    

    So the Content="Contentx" works because the second lable's Content property is not overridden while the first label's Content was. I guess that had the first not been overridden then the two Labels would have both displayed the same Content.

    The Tag="Captionx" works because the first label's Content is bound to the tag although the Binding details are beyond my understanding. I'm guessing that a human readable interpretation of the above would be 'this element's Content property will be filled with the value that resides in the Tag property of the first parent of type ContentControl associated with this particular UI element.' In this case, the parent is the Label whose style is being overridden.

    Wicked good! Thanks.


    Richard Lewis Haggard

    Thursday, October 04, 2012 5:44 PM
  • You can make a gal's head spin.

    :) Glad you got it worked out.

    ~Christine


    My Gallery

    Thursday, October 04, 2012 6:39 PM
  • @Mr Javaman II Just reading it, I couldn't decide if he wanted to display 50 items of a collection, or 50 labels spread across 50 views. The first solution I provided is great for LOB, because I get right sick of typing Grid.Row="2" Grid.Column="0", and reordering large forms is just a nightmate. The second massively reduces XAML and allows you too expose XAML properties of the TextBlocks if you wanted to. I think I'm just a fan of keeping the XAML to a minimum, I'm much happier with a larger chunk of codebehind, but cheers for the feedback. Horses for courses perhaps?

    Friday, October 05, 2012 12:30 AM
  • Matt;

      Thanks for the insight, I know exactly what you are talking about concerning re-ordering Grid Rows (mostly done) in large grids.  It's an absolute nightmare.  So thanks for pointing that out in your code, I've put that idea into the toolbox.

      Yes I too especially for trying to implement tons of controls based on a collection will often just throw in a StackPanel into XAML and then create and push in all the controls from codebehind.

    P.S.  MSFT should allow Grid Rows to be named!  That would fix this numeric ordering problem.


    JP Cowboy Coders Unite!


    Friday, October 05, 2012 1:36 PM