programing

명명 된 콘텐츠로 WPF UserControl을 만드는 방법

nasanasas 2020. 9. 8. 08:04
반응형

명명 된 콘텐츠로 WPF UserControl을 만드는 방법


동일한 방식으로 지속적으로 재사용되는 연결된 명령 및 논리가있는 컨트롤 집합이 있습니다. 모든 공통 컨트롤과 논리를 포함하는 사용자 컨트롤을 만들기로 결정했습니다.

그러나 이름을 지정할 수있는 콘텐츠를 보유 할 수있는 컨트롤도 필요합니다. 다음을 시도했습니다.

<UserControl.ContentTemplate>
    <DataTemplate>
        <Button>a reused button</Button>
        <ContentPresenter Content="{TemplateBinding Content}"/>
        <Button>a reused button</Button>
    </DataTemplate>
</UserControl.ContentTemplate>

그러나 사용자 정의 컨트롤 내부에있는 콘텐츠는 이름을 지정할 수 없습니다. 예를 들어 다음과 같은 방식으로 컨트롤을 사용하는 경우 :

<lib:UserControl1>
     <Button Name="buttonName">content</Button>
</lib:UserControl1>

다음과 같은 오류가 발생합니다.

'Button'요소에 이름 속성 값 'buttonName'을 설정할 수 없습니다. 'Button'은 다른 범위에서 정의되었을 때 이미 이름이 등록 된 'UserControl1'요소의 범위에 있습니다.

buttonName을 제거하면 컴파일되지만 콘텐츠의 이름을 지정할 수 있어야합니다. 이것을 어떻게 할 수 있습니까?


대답은 UserControl을 사용하지 않는 것입니다.

ContentControl 을 확장하는 클래스 만들기

public class MyFunkyControl : ContentControl
{
    public static readonly DependencyProperty HeadingProperty =
        DependencyProperty.Register("Heading", typeof(string),
        typeof(HeadingContainer), new PropertyMetadata(HeadingChanged));

    private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((HeadingContainer) d).Heading = e.NewValue as string;
    }

    public string Heading { get; set; }
}

그런 다음 스타일을 사용하여 내용을 지정하십시오.

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyContainer">
                <Grid>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

그리고 마지막으로-그것을 사용하십시오

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>

XAML을 사용하면 불가능한 것 같습니다. 사용자 지정 컨트롤은 실제로 필요한 모든 컨트롤을 가지고 있지만 약간의 논리와 함께 그룹화하고 명명 된 콘텐츠를 허용하면되는 경우 과잉 인 것 같습니다.

mackenir이 제안한대로 JD의 블로그 에있는 솔루션 은 최상의 타협을하는 것 같습니다. XAML에서 컨트롤을 계속 정의 할 수 있도록 JD의 솔루션을 확장하는 방법은 다음과 같습니다.

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        var grid = new Grid();
        var content = new ContentPresenter
                          {
                              Content = Content
                          };

        var userControl = new UserControlDefinedInXAML();
        userControl.aStackPanel.Children.Add(content);

        grid.Children.Add(userControl);
        Content = grid;           
    }

In my example above I have created a user control called UserControlDefinedInXAML which is define like any normal user controls using XAML. In my UserControlDefinedInXAML I have a StackPanel called aStackPanel within which I want my named content to appear.


Another alternative I've used is to just set the Name property in the Loaded event.

In my case, I had a rather complex control which I didn't want to create in the code-behind, and it looked for an optional control with a specific name for certain behavior, and since I noticed I could set the name in a DataTemplate I figured I could do it in the Loaded event too.

private void Button_Loaded(object sender, RoutedEventArgs e)
{
    Button b = sender as Button;
    b.Name = "buttonName";
}

Sometimes you might just need to reference the element from C#. Depending on the use case, you can then set an x:Uid instead of an x:Name and access the elements by calling a Uid finder method like Get object by its Uid in WPF.


You can use this helper for set name inside the user control:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Media;
namespace UI.Helpers
{
    public class UserControlNameHelper
    {
        public static string GetName(DependencyObject d)
        {
            return (string)d.GetValue(UserControlNameHelper.NameProperty);
        }

        public static void SetName(DependencyObject d, string val)
        {
            d.SetValue(UserControlNameHelper.NameProperty, val);
        }

        public static readonly DependencyProperty NameProperty =
            DependencyProperty.RegisterAttached("Name",
                typeof(string),
                typeof(UserControlNameHelper),
                new FrameworkPropertyMetadata("",
                    FrameworkPropertyMetadataOptions.None,
                    (d, e) =>
                    {
                        if (!string.IsNullOrEmpty((string)e.NewValue))
                        {
                            string[] names = e.NewValue.ToString().Split(new char[] { ',' });

                            if (d is FrameworkElement)
                            {
                                ((FrameworkElement)d).Name = names[0];
                                Type t = Type.GetType(names[1]);
                                if (t == null)
                                    return;
                                var parent = FindVisualParent(d, t);
                                if (parent == null)
                                    return;
                                var p = parent.GetType().GetProperty(names[0], BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty);
                                p.SetValue(parent, d, null);
                            }
                        }
                    }));

        public static DependencyObject FindVisualParent(DependencyObject child, Type t)
        {
            // get parent item
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);

            // we’ve reached the end of the tree
            if (parentObject == null)
            {
                var p = ((FrameworkElement)child).Parent;
                if (p == null)
                    return null;
                parentObject = p;
            }

            // check if the parent matches the type we’re looking for
            DependencyObject parent = parentObject.GetType() == t ? parentObject : null;
            if (parent != null)
            {
                return parent;
            }
            else
            {
                // use recursion to proceed with next level
                return FindVisualParent(parentObject, t);
            }
        }
    }
}

and your Window or Control Code Behind set you control by Property:

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

    }

    public Button BtnOK { get; set; }
}

your window xaml:

    <Window x:Class="user_Control_Name.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:test="clr-namespace:user_Control_Name"
            xmlns:helper="clr-namespace:UI.Helpers" x:Name="mainWindow"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <test:TestUserControl>
                <Button helper:UserControlNameHelper.Name="BtnOK,user_Control_Name.MainWindow"/>
            </test:TestUserControl>
            <TextBlock Text="{Binding ElementName=mainWindow,Path=BtnOK.Name}"/>
        </Grid>
    </Window>

UserControlNameHelper get your control name and your Class name for set Control to Property.


I've chosen to create an extra property for each element I need to get:

    public FrameworkElement First
    {
        get
        {
            if (Controls.Count > 0)
            {
                return Controls[0];
            }
            return null;
        }
    }

This enables me to access the child elements in XAML:

<TextBlock Text="{Binding First.SelectedItem, ElementName=Taxcode}"/>

<Popup>
    <TextBox Loaded="BlahTextBox_Loaded" />
</Popup>

Code behind:

public TextBox BlahTextBox { get; set; }
private void BlahTextBox_Loaded(object sender, RoutedEventArgs e)
{
    BlahTextBox = sender as TextBox;
}

The real solution would be for Microsoft to fix this issue, as well as all the others with broken visual trees etc. Hypothetically speaking.


Yet another workaround: reference the element as RelativeSource.


I had the same problem using a TabControl when placing a bunch of named controls into.

My workaround was to use a control template which contains all my controls to be shown in a tab page. Inside the template you can use the Name property and also data bind to properties of the named control from other controls at least inside the same template.

As Content of the TabItem Control, use a simple Control and set the ControlTemplate accordingly:

<Control Template="{StaticResource MyControlTemplate}"/>

Accessing those named control inside the template from code behind you would need to use the visual tree.

참고URL : https://stackoverflow.com/questions/751325/how-to-create-a-wpf-usercontrol-with-named-content

반응형