question

RogerSchlueter-7899 avatar image
0 Votes"
RogerSchlueter-7899 asked ·

Need help writing a UserControl

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-wpf
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

AlexLi-MSFT avatar image
0 Votes"
AlexLi-MSFT answered ·

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 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

After some adjustments to my case, I finally got it with this help. Thanks.

0 Votes 0 · ·

Ignore that comment - I can't get the event to trigger. In my wpf window, I have the following code:

 Dim dv As New CalendarDay
 dv.lblDay.Content = "Some Text
 AddHandler dv.pnlDay.MouseLeftButtonUp, AddressOf AddItem

And

 Private Sub AddItem(sender As Object, e As MouseButtonEventArgs)
         ....
 End Sub

Unfortunatelym AddItem is never fired. Can you see why?

0 Votes 0 · ·
AlexLi-MSFT avatar image AlexLi-MSFT RogerSchlueter-7899 ·

You create a new CalendaryDay,but I don't see your code to add CalendaryDay to MainWindow.

0 Votes 0 · ·
PeterFleischer-3316 avatar image
1 Vote"
PeterFleischer-3316 answered ·

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
· 8 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Immense help. I understand how the various parts work together now. Just one queston: In some articles this line is added to the code-behiand:

 Inherits UserControl

Is that necessary? A good idea?

0 Votes 0 · ·

Hi, in VB.NET, unlike C # .NET, it is not necessary to inherit UserControl.

0 Votes 0 · ·

Regarding the link, my German is decades old so I'm not likely to get much insight from there. :D

0 Votes 0 · ·
Show more comments

I've marked this as the answer but I hope you'll entertain a related question. Up to now I've kept the question to the essence of what I'm trying to do but now I want to embellish my UserControl with, for example, a trigger on the contents of the label. Assume I name the Label x:Name="lbl". The trigger I'd like to add (in pseudo-code):

 <Style.Triggers>
     <Trigger Property="lbl.Content" Value="Date.Now.Month">
         <Several Setters>
     </Trigger> 
 </Style.Triggers>

I can't figure out how to write either the Property nor Value in spite of numerous attempts.

0 Votes 0 · ·

Hi Roger, for test purposes try following demo. I insert a binded TextBox for changing the lblDay string. Write "Date:" and you get red background. lblDay must raise PropertyChanged event.

 ...
           <Label Content="{Binding lblDay}">
             <Label.Style>
               <Style TargetType="Label">
                 <Style.Triggers>
                   <Trigger Property="Content" Value="Date:">
                     <Setter Property="Background" Value="Red"/>
                   </Trigger>
                 </Style.Triggers>
               </Style>
             </Label.Style>
           </Label>
         </StackPanel>
       </DockPanel>
     </Border>
     <TextBox Text="{Binding lblDay, UpdateSourceTrigger=PropertyChanged}"/>
   </StackPanel>
0 Votes 0 · ·

Sorry, I'm afraid my question was unclear. I know how to write simple Triggers but in this particular case, I don't know how to state the two parts of the trigger.

For the value part, instead of writing:

 Value="Date.Now.Month"

I think it should be something like:

 Value="{x:Static sys:Date.Now.Month}"

but even that gives a compile-time error.

How should these parts be written? What I am aiming for is for the trigger to fire when lbl.content, expressed as an integer, is the same as the current month. For example for this month, I want the trigger to fire when lbl.Content equals 4.



0 Votes 0 · ·

Hi Peter how would you go about binding to the public properties for the user control from a viewmodel?

regards,
Shaban

0 Votes 0 · ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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
· Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

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
· 1 · Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

That question was from another user, but the information is useful in any case.

0 Votes 0 · ·