简体   繁体   中英

expose a field in wpf user control for external use

I am very new to wpf and I would like to create a simple user control that consists of a textblock and a textbox so that I can reuse it. However, I do not really know how to bind the content of the textblock so that it can be set from outside and expose the textbox so that it could be bound to other field from the outside calling xaml.

The following is the code for my user control

<UserControl x:Class="WPFLib.UserControlLibs.TextBoxUsrCtrl"
                         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:WPFLib.UserControlLibs"
                         mc:Ignorable="d"
                         d:DesignHeight="20"
                         d:DesignWidth="300">
    <StackPanel Orientation='Horizontal'
                            Width='{Binding ActualWidth, ElementName=parentElementName}'
                            Height='{Binding ActualWidth, ElementName=parentElementName}'>
        <Grid HorizontalAlignment='Stretch'>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width='1*' />
                <ColumnDefinition Width='1*' />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <TextBlock Text='{Binding Text, ElementName=parentElementName}'
                                 Background='Aqua'
                                 Grid.Column='0'
                                 Grid.Row='0' />
            <TextBox x:Name='UserTxBox'
                             Grid.Column='1'
                             Grid.Row='0'
                             Background='Red'
                             HorizontalAlignment='Stretch'
                             Text='this is a test to see how it works' />
        </Grid>
    </StackPanel>
</UserControl>

How do I expose the Text from the TextBlock and TextBox so that it could be set and retrieved from the calling xaml?

For example

        <Window x:Class="TestWPF.MainWindow"
                        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:TestWPF"
                          xmlns:controls='clr-namespace:WPFLib.UserControlLibs'
    `                   mc:Ignorable="d"
                        Title="MainWindow"
                        Height="350"
                        Width="525"
                        WindowState='Maximized'
                        FontSize='18'>
            <StackPanel>                                    
<controls:TextBoxUsrCtrl Width='500' HorizontalAlignment='Left' **Text='NEED TO SET THE TEXT BLOCK HERE'**/>
            </StackPanel>
        </Window>

You should give it two dependency properties, one for each of the two text properties you want to expose (this is a horrible amount of boilerplate; I use Visual Studio's snippet feature to generate it all). Then in the UserControl XAML, you bind control properties to those.

public partial class TextBoxUsrCtrl : UserControl
{
    public TextBoxUsrCtrl()
    {
        InitializeComponent();
    }

    #region Text Property
    public String Text
    {
        get { return (String)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(nameof(Text), typeof(String), typeof(TextBoxUsrCtrl),
            new FrameworkPropertyMetadata(null) {
                //  It's read-write, so make it bind both ways by default
                BindsTwoWayByDefault = true
            });
    #endregion Text Property

    #region DisplayText Property
    public String DisplayText
    {
        get { return (String)GetValue(DisplayTextProperty); }
        set { SetValue(DisplayTextProperty, value); }
    }

    public static readonly DependencyProperty DisplayTextProperty =
        DependencyProperty.Register(nameof(DisplayText), typeof(String), typeof(TextBoxUsrCtrl),
            new PropertyMetadata(null));
    #endregion DisplayText Property
}

XAML. I simplified this so the layout works as I think you intended.

Note how the bindings use RelativeSource={RelativeSource AncestorType=UserControl} to bind to the dependency properties we defined above on the UserControl . By default, Binding will bind to properties of UserControl.DataContext , but we're not using that. The reason is that if we set UserControl.DataContext here, that will break the viewmodel property bindings in the final XAML fragment at the end of this answer. Those bindings will look for those properties on our control . There are workarounds but it gets ugly. The way I've done it here is best because it never breaks anybody's assumptions about DataContext inheritance.

<UserControl 
    x:Class="WPFLib.UserControlLibs.TextBoxUsrCtrl"
    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:WPFLib.UserControlLibs"
    mc:Ignorable="d"
    d:DesignHeight="20"
    d:DesignWidth="300"
    >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width='*' />
            <ColumnDefinition Width='*' />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock 
            Text='{Binding DisplayText, RelativeSource={RelativeSource AncestorType=UserControl}}'
            Background='Aqua'
            Grid.Column='0'
            Grid.Row='0' 
            />
        <TextBox 
            x:Name='UserTxBox'
            Grid.Column='1'
            Grid.Row='0'
            Background='Red'
            HorizontalAlignment='Stretch'
            Text='{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}'
            />
    </Grid>
</UserControl>

Usage in window, bound to viewmodel properties:

    <local:TextBoxUsrCtrl
        Text="{Binding TestText}"
        DisplayText="{Binding ShowThisText}"
        />

Lastly, I'm not sure what you were getting at with ElementName=parentElementName . If that's meant to be a reference to a parent control, you can't do that, and it wouldn't be a good idea if you could. You wouldn't want a UserControl constrained by a requirement that a parent control must have a particular name. The answer to that requirement is simply that controls in XAML are only responsible for sizing themselves if they have a fixed size. If they should size to the parent, the parent is always the one responsible for that. So if you want to size two instances of TextBoxUsrCtrl to two different parents, that's fine. Each parent sizes its own children as it pleases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM