Need help writing a UserControl

RogerSchlueter-7899 1,176 Reputation points
2020-04-16T21:42:02.433+00:00

I trying to create a UserControl that will be added a variable number of times to a wpf window at run-time. This is my first attempt at a UserControl. I have two requirements for this UserControl:

  • The content of the label is to be defined at run-time.
  • The StackPanel should respond to a click event at run-time.

Here is my XAML:

<UserControl
    x:Class="CalendarDay"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d" 
    d:DesignHeight="150"
    d:DesignWidth="100">
    <Border
        BorderThickness="1"
        BorderBrush="Gray">
        <DockPanel>
            <StackPanel
                DockPanel.Dock="Top"
                Orientation="Horizontal">
                <Label
                    x:Name="lblTopic"
                    FontSize="16"
                    FontWeight="DemiBold"
                    HorizontalAlignment="Left">
                </Label>
            </StackPanel>
        </DockPanel>
    </Border>
</UserControl>

My specific questions are:

  1. How to expose the Content property of the label so it can be set at run-time? This value will not change for the lifetime of the control nor will it be changeable by the user.
  2. How to expose the MouseLeftButtonUp Event of the StackPanel.

Thanks in advance.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,665 questions
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-04-17T07:25:58.87+00:00

    Hi, All controls in a user control are not visible from the outside. You have to give UserControl its own public properties. Try following demo.

    XAML UserControl:

    <UserControl x:Class="CalendarDay"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Border BorderThickness="1" 
              BorderBrush="Gray">
        <DockPanel>
          <StackPanel x:Name="pnlDay"
                      DockPanel.Dock="Top"
                      Orientation="Horizontal">
            <Label Content="{Binding lblDay}"  
                   FontSize="16"
                   FontWeight="DemiBold"
                   HorizontalAlignment="Left">
            </Label>
          </StackPanel>
        </DockPanel>
      </Border>
    </UserControl>
    

    CodeBehind UserControl:

    Public Class CalendarDay
    
      Public Event pnlDayMouseLeftButtonUp As EventHandler
    
      Public Property lblDay As String
    
      Private Sub pnlDay_Loaded(sender As Object, e As RoutedEventArgs) Handles pnlDay.Loaded
        Me.DataContext = Me
      End Sub
    
      Private Sub pnlDay_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles pnlDay.MouseLeftButtonUp
        RaiseEvent pnlDayMouseLeftButtonUp(Me, New EventArgs)
      End Sub
    
    End Class
    

    Using in MainWindows CodeBehind:

    Imports WpfControlLibrary1
    
    Public Class MainWindow
      Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Dim dv As New CalendarDay
        dv.lblDay = "Some Text"
        AddHandler dv.pnlDayMouseLeftButtonUp, AddressOf AddItem
        Me.grd.Children.Add(dv) ' Grid in XAML: x:Name="grd" '
      End Sub
    
      Private Sub AddItem(sender As Object, e As EventArgs)
        Debug.Print("pnlDayMouseLeftButtonUp")
      End Sub
    
    End Class
    
    1 person found this answer helpful.

  2. Alex Li-MSFT 1,096 Reputation points
    2020-04-17T01:47:47.66+00:00

    Welcome to our Microsoft Q&A platform!

    If you want to set the Content property of the label and response the MouseLeftButtonUp Event of the StackPanel,see the following code:

    <Border
             BorderThickness="1"
             BorderBrush="Gray">
            <DockPanel>
                <StackPanel
                    x:Name="stackPanel1"
                     DockPanel.Dock="Top"
                     Orientation="Horizontal">
                    <Label
                         x:Name="lblTopic"
                        Content="lb1"
                         FontSize="16"
                         FontWeight="DemiBold"
                         HorizontalAlignment="Left">
                    </Label>
                </StackPanel>
            </DockPanel>
        </Border>
    
     <StackPanel>
            <local:UserControl1 x:Name="userControl1"/>
            <Button Content="btn" Click="Button_Click"/>
        </StackPanel>
    
    
     public MainWindow()
            {
                InitializeComponent();
                //MouseLeftButtonUp Event
                userControl1.stackPanel1.MouseLeftButtonUp += (a, b) => 
                {
                   ....
                };
            }
              //change lblTopic.Content property
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                userControl1.lblTopic.Content = "test";
            }
    

    Thanks.


  3. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-04-19T04:48:34.717+00:00

    Hi Roger, it is impossible to bind trigger value. You can use a multi value binding with converter and set trigger value to result of converter. Try following demo:

    XAML UserControl:

    <UserControl x:Class="Window006UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <UserControl.Resources>
        <local:Window006UC1Converter x:Key="conv"/>
      </UserControl.Resources>
      <StackPanel>
        <Border BorderThickness="1" 
              BorderBrush="Gray">
          <DockPanel>
            <StackPanel x:Name="pnlDay"
                      DockPanel.Dock="Top"
                      Orientation="Horizontal">
              <Label Content="{Binding lblDay}"  
                   FontSize="16"
                   FontWeight="DemiBold"
                   HorizontalAlignment="Left">
                <Label.Style>
                  <Style TargetType="Label">
                    <Setter Property="Background" Value="LightPink"/>
                    <Style.Triggers>
                      <DataTrigger Value="True">
                        <DataTrigger.Binding>
                          <MultiBinding Converter="{StaticResource conv}">
                            <Binding RelativeSource="{RelativeSource self}" Path="DataContext.lblDay" Mode="OneWay" />
                            <Binding RelativeSource="{RelativeSource self}" Path="DataContext.Month" Mode="OneWay"/>
                          </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter Property="Background" Value="LightGreen"/>
                      </DataTrigger>
                    </Style.Triggers>
                  </Style>
                </Label.Style>
              </Label>
            </StackPanel>
          </DockPanel>
        </Border>
        <!-- Datepicker for tests-->
        <DatePicker SelectedDate="{Binding lblDay}"/>
      </StackPanel>
    </UserControl>
    

    Code:

    Imports System.ComponentModel
    Imports System.Globalization
    Imports System.Runtime.CompilerServices
    
    Public Class Window006UC1
      Implements INotifyPropertyChanged
    
      Private Sub pnlDay_Loaded(sender As Object, e As RoutedEventArgs) Handles pnlDay.Loaded
        Me.DataContext = Me
      End Sub
    
      Public Event pnlDayMouseLeftButtonUp As EventHandler
    
      Private _lblDay As Date
      Public Property lblDay As Date
        Get
          Return Me._lblDay
        End Get
        Set(value As Date)
          Me._lblDay = value
          OnPropChanged()
        End Set
      End Property
    
      Public Property Month As Integer
    
      Private Sub pnlDay_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles pnlDay.MouseLeftButtonUp
        RaiseEvent pnlDayMouseLeftButtonUp(Me, New EventArgs)
      End Sub
    
    
      Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      Private Sub OnPropChanged(<CallerMemberName> Optional propName As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
      End Sub
    
    End Class
    
    Public Class Window006UC1Converter
      Implements IMultiValueConverter
    
      Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
        Dim p1 As Date
        If values.Count <> 2 OrElse Not Date.TryParse(values(0).ToString, p1) Then Exit Function
        Dim p2 As Integer
        If Not Integer.TryParse(values(1).ToString, p2) Then Exit Function
        Return p1.Month = p2
      End Function
    
      Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class
    
    0 comments No comments

  4. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-04-24T09:37:57.74+00:00

    Hi Roger, for binding properties from ViewModel to properties in UserControl you must use DependencyProperties and the correct DataContext. Try following demo.

    XAML MainWindow:
    
    <Window x:Class="Window011"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1.WpfApp011"
            xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
            mc:Ignorable="d"
            Title="Window011" Height="450" Width="800">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Window.Resources>
        <local:TextToIntConverter x:Key="conv1"/>
      </Window.Resources>
      <StackPanel>
        <Border BorderBrush="Gray" BorderThickness="3" Margin="5">
          <StackPanel>
            <!-- DataPicker and TextBox for binding test-->
          <DatePicker SelectedDate="{Binding DayValue}" Margin="5"/>
          <TextBox x:Name="tb" Text="1" Margin="5"/>
      </StackPanel>
      </Border>
        <Border BorderBrush="Red" BorderThickness="3" Margin="5">
          <uc:Window011UC1 LblDay="{Binding DayValue}" LblMonth="{Binding Text, ElementName=tb, Converter={StaticResource conv1}}" />
        </Border>
      </StackPanel>
    </Window>
    
    ViewModel:
    
    Imports System.Globalization
    
    Namespace WpfApp011
      Public Class ViewModel
    
        Public Property DayValue As Date = Now
    
        Public Property MonthValue As Integer = 5
    
      End Class
    
      Public Class TextToIntConverter
        Implements IValueConverter
    
        Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
          Dim n As Integer
          If Integer.TryParse(value?.ToString, n) Then Return n
          Return 0
        End Function
    
        Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
          Return value?.ToString
        End Function
      End Class
    
    End Namespace
    
    XAML UserControl:
    
    <UserControl x:Class="Window011UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfControlLibrary1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <UserControl.Resources>
        <local:Window011UC1Converter x:Key="conv2"/>
      </UserControl.Resources>
      <StackPanel x:Name="sp">
        <Border BorderThickness="1" 
              BorderBrush="Gray">
          <DockPanel>
            <StackPanel x:Name="pnlDay"
                      DockPanel.Dock="Top"
                      Orientation="Horizontal">
              <Label Content="{Binding LblDay}"  
                   FontSize="16"
                   FontWeight="DemiBold"
                   HorizontalAlignment="Left">
                <Label.Style>
                  <Style TargetType="Label">
                    <Setter Property="Background" Value="LightPink"/>
                    <Style.Triggers>
                      <DataTrigger Value="True">
                        <DataTrigger.Binding>
                          <MultiBinding Converter="{StaticResource conv2}">
                            <Binding RelativeSource="{RelativeSource self}" Path="DataContext.LblDay" Mode="OneWay" />
                            <Binding RelativeSource="{RelativeSource self}" Path="DataContext.LblMonth" Mode="OneWay"/>
                          </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter Property="Background" Value="LightGreen"/>
                      </DataTrigger>
                    </Style.Triggers>
                  </Style>
                </Label.Style>
              </Label>
            </StackPanel>
          </DockPanel>
        </Border>
        <!-- Datepicker for tests-->
        <DatePicker SelectedDate="{Binding LblDay}"/>
      </StackPanel>
    </UserControl>
    
    CodeBehind UserControl:
    
    Imports System.Globalization
    
    Public Class Window011UC1
    
      Public Sub New()
    
        ' This call is required by the designer.
        InitializeComponent()
    
        ' Add any initialization after the InitializeComponent() call.
        sp.DataContext = Me
      End Sub
    
      Public Shared ReadOnly LblDayProperty As DependencyProperty =
        DependencyProperty.RegisterAttached(
        "LblDay",
        GetType(Date),
        GetType(Window011UC1),
        New UIPropertyMetadata(Now))
    
      Public Shared Function GetLblDay(tvi As TreeViewItem) As Date
        Return CType(tvi.GetValue(LblDayProperty), Date)
      End Function
    
      Public Shared Sub SetLblDay(tvi As TreeViewItem, value As Date)
        tvi.SetValue(LblDayProperty, value)
      End Sub
    
      Public Shared ReadOnly LblMonthProperty As DependencyProperty =
        DependencyProperty.RegisterAttached(
        "LblMonth",
        GetType(Integer),
        GetType(Window011UC1),
        New UIPropertyMetadata(0))
    
      Public Shared Function GetLblMonth(tvi As TreeViewItem) As Integer
        Return CType(tvi.GetValue(LblMonthProperty), Integer)
      End Function
    
      Public Shared Sub SetLblMonth(tvi As TreeViewItem, value As Integer)
        tvi.SetValue(LblMonthProperty, value)
      End Sub
    
    End Class
    
    Public Class Window011UC1Converter
      Implements IMultiValueConverter
    
      Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
        Dim p1 As Date
        If values.Count <> 2 OrElse Not Date.TryParse(values(0).ToString, p1) Then Exit Function
        Dim p2 As Integer
        If Not Integer.TryParse(values(1).ToString, p2) Then Exit Function
        Return p1.Month = p2
      End Function
    
      Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
        Throw New NotImplementedException()
      End Function
    End Class