none
Creating a generic User Control and binding to its controls

    Dotaz

  • Hi all,

    I should probably mention I'm relatively new to WPF and do most of my user interface creation via Visual Studio. It could well be my question is answered by some simple XAML, but I'm still getting to grips with it, so bare with me =)

    I'm a little confused as to how to make a generic user control. That is to say that I want to create a control with no specific purpose, but could be used in multiple projects for multiple purposes. Just to try and explain what I mean I will use the following example:

    Say I create a login user control with 2 labels: 'lblUsername' and 'lblPassword'
    and 2 text boxes 'dataUsername' and 'dataPassword'
    The user control itself can be given a name of 'LoginControl'

    Making the control is easy enough, but if I then add this control to a window I find that I can set the data context of the control, but cannot select the individual text boxes to set which properties to bind to. For example I would love to be able to do something like:

    <LoginControl x:Name="login" DataContext="{StaticResource user}"> <LoginControl.dataUsername Text="{Binding Username}"/>
    <LoginControl.dataPassword Text="{Binding Password}">
    </LoginControl>


    Or something to that effect...not sure if my XAML is correct there...but you get the idea. Either I'm doing something wrong or the above is just not possible =(
    Depending on who uses the control, or which project it is in, the binding may be to a property called 'Username' / 'LoginName' / 'User' / 'DisplayName' etc etc. So ideally I would not like to make a user conform to any type of template that I have made and rather just allow them access to the control itself. They may even want to change the foreground colour for example so I would like to expose ALL properties of that control, not just text as the example above shows. Can I expose all of these properties for a particular control inside a user control?

    If so how is this possible?

    Thanks in advance

    11. května 2012 18:18

Odpovědi

  • What you need to do is create properties in your usercontrol that will map to the properties you wish control.  What you have as XAML will then work.  Regular properties will not work.

    The following link should provide you the info you need:

    http://www.webdesigncompany.co.uk/blog/2011/9/20/wpf-user-controls-turn-your-user-control-into-a-data-binding-target/

    Hope this helps

    Lloyd Sheen


    Lloyd Sheen

    11. května 2012 19:03
  • There are at least a dozen different ways to create reusable controls.  The most fundamental way is doing it like this:

    http://wpftutorial.net/HowToCreateACustomControl.html

    In this example you are as low level as you can get.  You are not reusing controls rather you are defining your own controls, content, properties, binding ability etc.

    The next layer is to ask yourself what is my control most similar to in the current collection of System.Windows.Controls and derive from that.  A lot of folks use the User control as a base.

    <UserControl x:Class="Borders.Controls.CustomUserControl"
                 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:cmd="clr-namespace:Borders.Commands"
                 Height="auto"
                 Width="150" xmlns:my="clr-namespace:Borders.Controls">
    
        <UserControl.Resources>
            <cmd:ASimpleCommand x:Key="CMD"></cmd:ASimpleCommand>
        </UserControl.Resources>
        <StackPanel DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:CustomUserControl, AncestorLevel=1}, Path=TheUser}">
            <TextBlock HorizontalAlignment="Center">User Id</TextBlock>
            <TextBox x:Name="XTBUser" Text="{Binding Path=TheUserName}"></TextBox>
            <TextBlock HorizontalAlignment="Center">Password</TextBlock>
            <TextBox x:Name="XTBPassword" Text="{Binding Path=TheUserPassword}"></TextBox>
            <Button x:Name="XBSubmit"
                    Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:CustomUserControl, AncestorLevel=1}, Path=TheCommand}">Submit</Button>
        </StackPanel>
    </UserControl>

    Then in the code behind you do this:

     public partial class CustomUserControl : UserControl
        {
            public CustomUserControl()
            {
                InitializeComponent();
                User user = new User { TheUserName = "Jumping", TheUserPassword = "Jehosiphat" };
                TheUser = new ObservableCollection<User>();
                TheUser.Add(user);
            }
     
            public static readonly DependencyProperty TheCommandProperty =
                DependencyProperty.Register("TheCommand"typeof(ICommand), typeof(CustomUserControl));
     
            public ICommand TheCommand
            {
                get { return (ICommand)GetValue(TheCommandProperty); }
                set { SetValue(TheCommandProperty, value); }
            }
     
            public static readonly DependencyProperty TheUserProperty =
                DependencyProperty.Register("TheUser"typeof(ObservableCollection<User>), typeof(CustomUserControl));
     
            public ObservableCollection<User> TheUser
            {
                get { return (ObservableCollection<User>)GetValue(TheUserProperty); }
                set { SetValue(TheUserProperty, value); }
            }
        }
     
        public class User
        {
            public string TheUserName { getset; }
     
            public string TheUserPassword { getset; }
        }

    Then in the consumer of this control you do this:

    <my10:CustomUserControl TheCommand="{Binding Source={StaticResource ASC}}">
        <my10:CustomUserControl.TheUser>
            <my10:User TheUserName="This is the user name" TheUserPassword="This is the user Password" />
        </my10:CustomUserControl.TheUser>
    </my10:CustomUserControl>

    But make sure the consumer control has a reference to the CustomUserControl:

    xmlns:cmd="clr-namespace:Borders.Commands"  
    xmlns:my10="clr-namespace:Borders.Controls"
    One is to the folder with the commands and the other is to the folder with the custom controls.


    JP Cowboy Coders Unite!

    11. května 2012 22:28

Všechny reakce

  • What you need to do is create properties in your usercontrol that will map to the properties you wish control.  What you have as XAML will then work.  Regular properties will not work.

    The following link should provide you the info you need:

    http://www.webdesigncompany.co.uk/blog/2011/9/20/wpf-user-controls-turn-your-user-control-into-a-data-binding-target/

    Hope this helps

    Lloyd Sheen


    Lloyd Sheen

    11. května 2012 19:03
  • Thanks Lloyd, I think that has cleared things up, but just so that I haven't misunderstood:

    Am I right in saying that I would need to create properties inside the user control for any property I would want the programmer to have access to, even if they already existed? For example for dataUsername I would need to create a property for:

    dataUsername.Text
    dataUsername.Foreground
    dataUsername.Background
    dataUsername.FontSize

    etc...

    That seems kind of monstrous if my user control has lots of controls I want to expose all of the properties for!

    11. května 2012 21:43
  • You could expose the controls within the Usercontrol with properties but in the long run that will almost make the use of a Usercontrol redundant.

    I find that UserControls (Custom Controls) are best thought of as a "macro" GUI element that handles a piece of data.  The setting of properties of the internal controls is generally done within the UserControl itself.

    LS


    Lloyd Sheen

    11. května 2012 21:51
  • Shiny,

    Thanks again Lloyd.

    11. května 2012 22:02
  • There are at least a dozen different ways to create reusable controls.  The most fundamental way is doing it like this:

    http://wpftutorial.net/HowToCreateACustomControl.html

    In this example you are as low level as you can get.  You are not reusing controls rather you are defining your own controls, content, properties, binding ability etc.

    The next layer is to ask yourself what is my control most similar to in the current collection of System.Windows.Controls and derive from that.  A lot of folks use the User control as a base.

    <UserControl x:Class="Borders.Controls.CustomUserControl"
                 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:cmd="clr-namespace:Borders.Commands"
                 Height="auto"
                 Width="150" xmlns:my="clr-namespace:Borders.Controls">
    
        <UserControl.Resources>
            <cmd:ASimpleCommand x:Key="CMD"></cmd:ASimpleCommand>
        </UserControl.Resources>
        <StackPanel DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:CustomUserControl, AncestorLevel=1}, Path=TheUser}">
            <TextBlock HorizontalAlignment="Center">User Id</TextBlock>
            <TextBox x:Name="XTBUser" Text="{Binding Path=TheUserName}"></TextBox>
            <TextBlock HorizontalAlignment="Center">Password</TextBlock>
            <TextBox x:Name="XTBPassword" Text="{Binding Path=TheUserPassword}"></TextBox>
            <Button x:Name="XBSubmit"
                    Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:CustomUserControl, AncestorLevel=1}, Path=TheCommand}">Submit</Button>
        </StackPanel>
    </UserControl>

    Then in the code behind you do this:

     public partial class CustomUserControl : UserControl
        {
            public CustomUserControl()
            {
                InitializeComponent();
                User user = new User { TheUserName = "Jumping", TheUserPassword = "Jehosiphat" };
                TheUser = new ObservableCollection<User>();
                TheUser.Add(user);
            }
     
            public static readonly DependencyProperty TheCommandProperty =
                DependencyProperty.Register("TheCommand"typeof(ICommand), typeof(CustomUserControl));
     
            public ICommand TheCommand
            {
                get { return (ICommand)GetValue(TheCommandProperty); }
                set { SetValue(TheCommandProperty, value); }
            }
     
            public static readonly DependencyProperty TheUserProperty =
                DependencyProperty.Register("TheUser"typeof(ObservableCollection<User>), typeof(CustomUserControl));
     
            public ObservableCollection<User> TheUser
            {
                get { return (ObservableCollection<User>)GetValue(TheUserProperty); }
                set { SetValue(TheUserProperty, value); }
            }
        }
     
        public class User
        {
            public string TheUserName { getset; }
     
            public string TheUserPassword { getset; }
        }

    Then in the consumer of this control you do this:

    <my10:CustomUserControl TheCommand="{Binding Source={StaticResource ASC}}">
        <my10:CustomUserControl.TheUser>
            <my10:User TheUserName="This is the user name" TheUserPassword="This is the user Password" />
        </my10:CustomUserControl.TheUser>
    </my10:CustomUserControl>

    But make sure the consumer control has a reference to the CustomUserControl:

    xmlns:cmd="clr-namespace:Borders.Commands"  
    xmlns:my10="clr-namespace:Borders.Controls"
    One is to the folder with the commands and the other is to the folder with the custom controls.


    JP Cowboy Coders Unite!

    11. května 2012 22:28
  • Woops need to show you how the command was bound:

    <TabItem Header="ICommand">
        <TabItem.Resources>
                <cmd:ASimpleCommand x:Key="ASC"></cmd:ASimpleCommand>
        </TabItem.Resources>
        <my10:CustomUserControl TheCommand="{Binding Source={StaticResource ASC}}">
            <my10:CustomUserControl.TheUser>
                <my10:User TheUserName="This is the user name" TheUserPassword="This is the user Password" />
            </my10:CustomUserControl.TheUser>
        </my10:CustomUserControl>
    </TabItem>

    And of course the command logic:

        internal class ASimpleCommand : ICommand
        {
            private bool _CanExecute = true;
    
            public bool CanExecute(object parameter)
            {
                return _CanExecute;
            }
    
            public event EventHandler CanExecuteChanged;
    
            public void Execute(object parameter)
            {
                _CanExecute = false;
                MessageBox.Show("This will delay 3 seconds after closing!");
                System.Threading.Thread.Sleep(3000);
                _CanExecute = true;
            }
        }
    I keep all my commands in their own folder, and implement what needs to be done. It keeps them separate. This example allows you to consume a customer control and set the properties at will including what the button does when it is pressed. I didn't show you how to pass in command parameters to the command let me know if you need that.


    JP Cowboy Coders Unite!

    11. května 2012 22:32
  • You may ask why dependency properties?  The answer is that they allow you to edit stuff in the properites window within the IDE..


    JP Cowboy Coders Unite!

    11. května 2012 22:37
  • A very subtle but cool point is that the Property Editor above know's it's a collection and what type is in the collection, on the right it reaches down into TheUser class and displays the properties automatically.  I used an ObservableCollection becuase any changes made reflect in the GUI immediately.

    JP Cowboy Coders Unite!

    11. května 2012 22:39
  • You can do the same thing for each text box to change the colors as you want.  Just make a dependencyproperty like shown above.  Add a CLR wrapper with getter and setter methods.  The cool thing about that is the property editor will show ALL of the properties of the button...  So the consumer has 100% control over look and feel of each button!

    JP Cowboy Coders Unite!

    11. května 2012 22:41
  • Woah, another great example! Many thanks =)
    12. května 2012 18:46