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


100

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

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

<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을 제거하면 컴파일되지만 콘텐츠의 이름을 지정할 수 있어야합니다. 이것을 어떻게 할 수 있습니까?


이것은 우연의 일치입니다. 이 질문을하려고했습니다! 나도 같은 문제가있어. 일반적인 UI 패턴을 UserControl로 분해하지만 이름으로 콘텐츠 UI를 참조하려고합니다.
mackenir 2009

3
이 사람 은 사용자 지정 컨트롤의 XAML 파일을 제거하고 프로그래밍 방식으로 사용자 지정 컨트롤의 UI를 빌드하는 것과 관련된 솔루션 을 찾았습니다 . 이 블로그 게시물 에는 주제에 대해 더 많은 이야기가 있습니다.
mackenir 2009

2
ResourceDictionary 방식을 사용하지 않는 이유는 무엇입니까? 그 안에 DataTemplate을 정의하십시오. 또는 BasedOn 키워드를 사용하여 컨트롤을 상속합니다. 그냥 몇 가지 경로가 나는 ... WPF의 코드 숨김 UI를하기 전에 따를 것
루이 KOTTMANN에게

답변:


46

대답은 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>

2
디자이너를 사용하여 일반 UserControl에서 ControlTemplate을 구체화하고 연결된 컨트롤 템플릿을 사용하여 스타일로 변환 할 수 있기 때문에 이것이 실제로 가장 편안한 솔루션이라는 것을 알았습니다.
Oliver Weichhold

5
흠,이 방법을 적용하려고 시도했지만 여전히이 악명 높은 오류가 발생하기 때문에 작동하는 것이 조금 이상합니다.
greenoldman 2011

8
@greenoldman @Badiboy 나는 그것이 당신을 위해 작동하지 않은 이유를 알고 있다고 생각합니다. 아마도 기존 코드를 UserControl에서 상속으로 변경했을 것입니다 ContentControl. 해결하려면 새 클래스 ( CS가있는 XAML이 아님) 를 추가하기 만하면 됩니다. 그리고 그것은 (희망적으로) 작동 할 것입니다. 만약 당신이 좋아하면, 나는 작은 만든 VS2010 솔루션
itsho

1
몇 년 후 나는 내가 :) 다시이 찬성 투표 수 있으면 좋겠다
드류 녹스

2
나는 추측 HeadingContainer하고 MyFunkyContainer될 예정 MyFunkyControl인가?!
Martin Schneider

20

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;           
    }

위의 예에서는 XAML을 사용하는 일반 사용자 컨트롤과 같이 정의되는 UserControlDefinedInXAML이라는 사용자 컨트롤을 만들었습니다. 내 UserControlDefinedInXAML에는 명명 된 콘텐츠가 표시되기를 원하는 aStackPanel이라는 StackPanel이 있습니다.


콘텐츠를 다시 양육하는이 메커니즘을 사용할 때 데이터 바인딩 문제가 발생한다는 것을 발견했습니다. 데이터 바인딩이 올바르게 설정된 것 같지만 데이터 원본에서 컨트롤의 초기 채우기가 제대로 작동하지 않습니다. 문제는 콘텐츠 발표자의 직접적인 자식이 아닌 컨트롤에만 국한되어 있다고 생각합니다.
mackenir 2009

그 이후로 이것을 실험 할 기회가 없었지만, UserControlDefinedInXAML (위의 예에서)에 정의 된 컨트롤이나 지금까지 ContentPresenter에 추가 된 컨트롤에 데이터 바인딩하는 데 문제가 없었습니다. 나는 코드를 통해서만 데이터 바인딩을 해왔다 (XAML이 아니라 차이가 있는지 확실하지 않음).
Ryan

차이를 만드는 것 같습니다. 방금 설명한 경우에 내 데이터 바인딩에 XAML을 사용해 보았지만 작동하지 않습니다. 그러나 코드로 설정하면 작동합니다!
Ryan

3

내가 사용한 또 다른 대안 NameLoaded이벤트 에서 속성을 설정하는 것 입니다.

제 경우에는 코드 숨김에서 만들고 싶지 않은 다소 복잡한 컨트롤이 있었고 특정 동작에 대한 특정 이름을 가진 선택적 컨트롤을 찾았습니다. 이벤트에서도 DataTemplate할 수 있다고 생각했습니다 Loaded.

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

2
이렇게하면 코드 뒤에 바인딩을 설정하지 않는 한 이름을 사용하는 바인딩이 작동하지 않습니다.
Tower

3

때로는 C #에서 요소를 참조해야 할 수도 있습니다. 사용 사례에 따라를 x:Uid대신 설정하고 WPF의 Uid로 Get 개체x:Name 와 같은 Uid 파인더 메서드를 호출하여 요소에 액세스 할 수 있습니다 .


1

이 도우미를 사용자 컨트롤 내부의 집합 이름에 사용할 수 있습니다.

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);
            }
        }
    }
}

창 또는 제어 코드 뒤에 속성으로 제어 할 수 있습니다.

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

    }

    public Button BtnOK { get; set; }
}

귀하의 창 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는 컨트롤을 속성으로 설정하기 위해 컨트롤 이름과 클래스 이름을 가져옵니다.


0

필요한 각 요소에 대해 추가 속성을 만들기로 선택했습니다.

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

이를 통해 XAML의 자식 요소에 액세스 할 수 있습니다.

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

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

뒤에있는 코드 :

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

실제 해결책은 Microsoft가이 문제를 해결하는 것뿐만 아니라 시각적 트리가 깨진 다른 모든 문제도 해결하는 것입니다.


0

또 다른 해결 방법 : 요소를 RelativeSource 로 참조하십시오 .


0

명명 된 컨트롤을 여러 개 배치 할 때 TabControl을 사용하여 동일한 문제가 발생했습니다.

내 해결 방법은 탭 페이지에 표시 할 모든 컨트롤이 포함 된 컨트롤 템플릿을 사용하는 것이 었습니다. 템플릿 내에서 Name 속성을 사용할 수 있으며 최소한 동일한 템플릿 내의 다른 컨트롤에서 명명 된 컨트롤의 속성에 데이터 바인딩 할 수도 있습니다.

TabItem 컨트롤의 콘텐츠로 간단한 컨트롤을 사용하고 그에 따라 ControlTemplate을 설정합니다.

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

뒤에있는 코드에서 템플릿 내부의 명명 된 컨트롤에 액세스하려면 시각적 트리를 사용해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.