简体   繁体   English

如何在自定义嵌套用户控件中引用另一个 XAML 元素?

[英]How to refer to another XAML element inside a custom nested user control?

I want to refer to another element, which resides as nested under a custom user control:我想引用另一个元素,它嵌套在自定义用户控件下:

<userControls:Test>
     <TextBox Name="Foo" />

     <Button CommandParameter="{Binding Text, ElementName=Foo" Command="{Binding anything}" />
</userControls:Test>

Basically I'm using commands and I'm trying to refer to another element with a name, but I get this famous:基本上我正在使用命令,我试图用一个名字来引用另一个元素,但我得到了这个著名的:

Cannot set Name attribute value '...' on element '...'.无法在元素“...”上设置名称属性值“...”。 '...' is under the scope of element '...', which already had a name registered when it was defined in another scope. '...' 在元素 '...' 的范围内,该元素在另一个范围内定义时已经注册了一个名称。

I figured it's impossible to name an element: How to create a WPF UserControl with NAMED content我认为不可能命名一个元素: How to create a WPF UserControl with NAMED content

So is there any other way to refer to the element that would work inside nested custom user control content?那么有没有其他方法可以引用在嵌套的自定义用户控件内容中工作的元素?

Not a direct answer to your question, but a reasonable workaround.不是您问题的直接答案,而是合理的解决方法。 In your view bind the Text to FooText property on the viewmodel, directly beside the declaration of the ICommand.在您的视图中,将 Text 绑定到视图模型上的 FooText 属性,就在 ICommand 的声明旁边。 When the command executes, use the latest FooText命令执行时,使用最新的FooText

<!-- View -->
<userControls:Test>
     <TextBox Text={Binding FooText, Mode=TwoWay} />

     <Button Command="{Binding FooCommand}" />
</userControls:Test>


// ViewModel
public string FooText { get; set; }

public ICommand FooCommand 
{ 
    get 
    { 
        return new DelegateCommand(() => ExecuteFoo(FooText)); 
    } 
} 

Why not make a template to the UserControl.为什么不为 UserControl 创建一个模板。

<....Resources>
    <Style TargetType="{x:Type UserControl}" x:Key="MyUserControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel>
                        <TextBox x:Name="PART_Foo"/>
                        <TextBlock Text="{Binding ElementName=PART_Foo, Path=Text}"/>
                        <ContentPresenter  Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content}"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</....Resources>
<UserControl Style="{StaticResource MyUserControl}">
</UserControl>

Then name the template with a key, and specify which UserControl uses which template.然后用key命名模板,指定哪个UserControl使用哪个模板。

Also, in your code, you could get the PART_Name of the templated part using:此外,在您的代码中,您可以使用以下方法获取模板化部分的 PART_Name:

UIElement GetUserControlPart(UserControl control, String name)
{
   return control.Template.FindName(name, control) as UIElement;
}

I ended up doing it like this:我最终这样做了:

<UserControl x:Class="MyProject.FooBar"
         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:userControls="clr-namespace:MyProject.UserControls"
         Loaded="Loaded"> <!-- notice -->

    <userControls:Test>
         <TextBox Initialized="FooInitialized" />

         <Button Initialized="AnotherInitialized" />
    </userControls:Test>

And code:和代码:

// This for every initialization, all we do is set names after the elements are initialized.
private void FooInitialized(object sender, EventArgs e)
{
    ((TextBox) sender).Name = "Foo";
}

// Here when the entire junk is loaded, we set the necessary commands.
private void Loaded(object sender, EventArgs e)
{
    // Find elements.
    var button = UIHelper.FindChild<Button>(this, "Bar");
    var textBox = UIHelper.FindChild<TextBox>(this, "Foo");

    // Set up bindings for button.
    button.Command = ((FooViewModel) DataContext).FooCommand;
    button.CommandParameter = textBox;
}

The UIHelper is from https://stackoverflow.com/a/1759923/283055 . UIHelper来自https://stackoverflow.com/a/1759923/283055 The button is also given a name the same way as the text box.该按钮也以与文本框相同的方式命名。

Basically all names are set via Initialized event.基本上所有名称都是通过Initialized事件设置的。 The only down side is that you must refer to the names in code, and that's why I'm setting commands via code after the entire component has loaded.唯一的缺点是您必须在代码中引用名称,这就是为什么我在整个组件加载后通过代码设置命令。 I found this the best compromise between code and UI markup.我发现这是代码和 UI 标记之间的最佳折衷。

If you use .NET 4.0 and require to set the binding source to the sibling control, then you could use this MarkupExtension:如果您使用 .NET 4.0 并要求将绑定源设置为同级控件,则可以使用此 MarkupExtension:

public class SiblingSourceBinding : MarkupExtension
{  
    public Binding Binding
    {
        get;
        set;
    }

    public int SiblingIndex
    {
        get;
        set;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

        if (provideValueTarget != null)
            this.Binding.Source = LogicalTreeHelper.GetChildren(LogicalTreeHelper.GetParent(provideValueTarget.TargetObject as DependencyObject)).Cast<object>().ElementAt(this.SiblingIndex);
        else
            throw new InvalidOperationException("Unable to set sibling binding source.");

        return this.Binding.ProvideValue(serviceProvider);
   }

and use it like this:并像这样使用它:

<userControls:Test>
    <TextBox/>
    <Button CommandParameter="{local:SiblingSourceBinding Binding={Binding Text}, SiblingIndex=0}" Command="{Binding anything}"/>
</userControls:Test>

You can use Uid in your nested element as in the following:您可以在嵌套元素中使用 Uid,如下所示:

<userControls:Test Name="usrCtrl">
     <TextBox Uid="Foo" />
     <Button Uid="btn"/>
</userControls:Test>

Then you use the following function (the function was a solution for this answer Get object by its Uid in WPF ):然后使用以下函数(该函数是此答案的解决方案Get object by its Uid in WPF ):

private static UIElement FindUid(this DependencyObject parent, string uid)
{
    var count = VisualTreeHelper.GetChildrenCount(parent);
    if (count == 0) return null;

    for (int i = 0; i < count; i++)
    {
        var el = VisualTreeHelper.GetChild(parent, i) as UIElement;
        if (el == null) continue;

        if (el.Uid == uid) return el;

        el = el.FindUid(uid);
        if (el != null) return el;
    }
    return null;
}

Then in your Loaded function add this:然后在您的 Loaded 函数中添加:

private void Loaded(object sender, EventArgs e)
{
    // Find elements.
    var button = FindUid(usrCtrl, "btn") as Button;
    var textBox = FindUid(usrCtrl, "Foo") as TextBox;
     
    // Do binding here...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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