source

NAMED 콘텐츠를 사용하여 WPF UserControl을 작성하는 방법

manycodes 2023. 4. 11. 22:13
반응형

NAMED 콘텐츠를 사용하여 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>

다음의 에러가 표시됩니다.

'버튼' 요소의 이름 특성 값 '버튼 이름'을 설정할 수 없습니다.'버튼'은 다른 스코프에서 정의될 때 이미 이름이 등록된 'UserControl1' 요소의 범위 아래에 있습니다.

buttonName을 삭제하면 컴파일 됩니다만, 컨텐츠의 이름을 지정할 수 있어야 합니다.어떻게 하면 좋을까요?

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

ContentControl을 확장하는 클래스를 만듭니다.

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

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

    public string Heading { get; set; }
}

그런 다음 스타일을 사용하여 내용을 지정합니다.

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyControl">
                <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을 사용하고 있는 경우는 불가능한 것 같습니다.커스텀 컨트롤은 실제로 필요한 컨트롤이 모두 갖추어져 있으면 과잉인 것 같습니다.단, 약간의 논리로 그룹화하여 명명된 콘텐츠를 허용하기만 하면 됩니다.

JD 블로그의 솔루션은 mackenir가 제시한 바와 같이 최선의 타협점을 가지고 있는 것 같습니다.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;           
    }

위의 예에서는 UserControlDefinedInX라는 사용자 컨트롤을 만들었습니다.AML은 XAML을 사용하는 일반 사용자 컨트롤과 동일하게 정의됩니다.My UserControlDefinedInX에서AML aStackPanel이라는 이름의 스택패널이 있는데, 이 스택패널 안에 내 이름 있는 콘텐츠를 표시하고 싶다.

제가 사용한 또 다른 대안은, 그냥 이 버젼을Name의 속성Loaded

배후에 않은 , 그을 에 할 수 되었기 DataTemplate는 그것을 할 수 것 같았다.Loaded이벤트도 있어요.

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

경우에 따라서는 C#의 요소만 참조할 필요가 있습니다. 사례에 따라서는 depending용사 an an an음, 음음 an an an an an an an an an an an an an an an an an an an an an an an an an an an 를 설정할 수 있습니다.x:Uid대신x:NameWPF에서 Get object by the Uid와 같은 Uid finder 메서드를 호출하여 요소에 액세스합니다.

이 도우미는 사용자 컨트롤 내의 설정 이름에 사용할 수 있습니다.

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>

사용자 제어명도우미는 Control을 Property로 설정하기 위한 컨트롤 이름과 클래스 이름을 가져옵니다.

필요한 각 요소에 대해 추가 속성을 생성하도록 선택했습니다.

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

그러면 XAML의 자 요소에 액세스할 수 있습니다.

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

코드 배면:

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

진정한 해결책은 Microsoft와 시각적인 트리가 파손된 다른 모든 문제를 해결하는 것입니다.가정해서 말하자면.

또 다른 회피책은 요소를 RelativeSource로 참조하는 것입니다.

여러 개의 명명된 컨트롤을 넣을 때 TabControl을 사용할 때도 동일한 문제가 있었습니다.

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

TabItem 컨트롤의 내용으로서 단순 컨트롤을 사용하여 ControlTemplate를 적절하게 설정합니다.

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

뒤에 있는 코드에서 템플릿 내의 이름 있는 컨트롤에 액세스하려면 비주얼 트리를 사용해야 합니다.

이 문제에 부딪혀 Xaml을 사용하여 커스텀 컨트롤을 설계할 수 있는 회피책을 찾았습니다.아직 약간의 해커가 있긴 하지만, 뚜렷한 타협 없이 제 모든 문제를 해결했습니다.

기본적으로는 xaml을 사용할 때와 같이 모든 작업을 수행하지만 제어 템플릿 자체에 헤더 선언의 일부를 포함하고 Base64는 코드 컨스트럭터에 로드되는 템플릿을 인코딩합니다.이 Xaml 발췌에는 나와 있지 않지만 완전한 Xaml이 사용한 네임스페이스는 실제로는 Controls 네임스페이스가 아닌 XamlTemplates를 대상으로 하고 있습니다.이는 개발용 디버깅 참조인 "Release" 빌드가 운영 환경에서 네임스페이스를 제어하기 위해 의도된 것입니다.자세한 것은, 이하를 참조해 주세요.

<ControlTemplate TargetType="{x:Type TabControl}" 
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid x:Name="templateRoot" 
          ClipToBounds="True" 
          SnapsToDevicePixels="True" 
          Background="Transparent"
          KeyboardNavigation.TabNavigation="Local">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="ColumnDefinition0"/>
            <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
            <RowDefinition x:Name="RowDefinition1" Height="*"/>
        </Grid.RowDefinitions>
        <TabPanel x:Name="HeaderPanel"
                  Panel.ZIndex="1"                          
                  Margin="{Binding MarginHeaderPanel, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                  IsItemsHost="True"                          
                  KeyboardNavigation.TabIndex="2"/>
        <Border x:Name="blankregion" Panel.ZIndex="1" Margin="0" Padding="0" 
                Background="{Binding Background, RelativeSource={RelativeSource AncestorType=TabControl}}">
            <ContentPresenter x:Name="blankpresenter"                                      
                              KeyboardNavigation.TabIndex="1"    
                              Content="{Binding TabBlankSpaceContent, RelativeSource={RelativeSource AncestorType=TabControl}}"                                          
                              ContentSource="TabBlankSpaceContent" 
                              SnapsToDevicePixels="True"/>
        </Border>

        <Grid x:Name="ContentPanel">
            <Border 
                BorderBrush="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=TabControl}}"
                BorderThickness="{Binding BorderThickness, RelativeSource={RelativeSource AncestorType=TabControl}}"                       
                Background="{Binding SelectedItem.Background, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.DirectionalNavigation="Contained" 
                KeyboardNavigation.TabNavigation="Local"                                           
                CornerRadius="{Binding BorderRadius, RelativeSource={RelativeSource AncestorType=TabControl}}"
                KeyboardNavigation.TabIndex="3">
                <ContentControl x:Name="PART_SelectedContentHost" 
                                ContentTemplate="{Binding SelectedContentTemplate, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                Content="{Binding SelectedContent, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                ContentStringFormat="{Binding SelectedContentStringFormat, RelativeSource={RelativeSource AncestorType=TabControl}}" 
                                Margin="{Binding Padding, RelativeSource={RelativeSource AncestorType=TabControl}}"
                                SnapsToDevicePixels="{Binding SnapsToDevicePixels, RelativeSource={RelativeSource AncestorType=TabControl}}"/>
            </Border>

        </Grid>
    </Grid>
    <ControlTemplate.Triggers>
        <!--Triggers were removed for clarity-->
    </ControlTemplate.Triggers>
</ControlTemplate>

위의 XAML은 파생된 컨트롤에 이름을 붙이지 않았으며 템플릿 내의 모든 것이 상대적인 룩업을 사용하여 속성을 바인딩했습니다.커스텀 룩업도 마찬가지입니다.

C#에서는 Xaml에서 Base64로 인코딩된 버전의 컨트롤 템플릿과 명령어를 사용하여 컨트롤의 개발/릴리스 버전을 셔플했습니다.이 토픽에 관한 문제에 대해서는, 개발 공간에 있어서의 나의 컨트롤이 문제가 되지 않고, 테스트/개발의 방법을 제공한다는 이론이 있습니다.릴리스 DLL 버전은 매우 정상적으로 동작하고 있는 것 같습니다.또, 디버깅/개발측과 같이, 빌드 된 컨트롤은 뛰어난 설계 시간 서포트를 갖추고 있습니다.

#if DEBUG
namespace AgileBIM.Controls
{
    public class AgileTabControl : AgileBIM.XamlTemplates.AgileTabControlDesigner { }
}

namespace AgileBIM.XamlTemplates
#else
namespace AgileBIM.Controls
#endif
{
#if DEBUG    
    public partial class AgileTabControlDesigner : TabControl
#else
    public class AgileTabControl : TabControl
#endif
    {

        

#if DEBUG
        private static Type ThisControl = typeof(AgileTabControlDesigner);
#else
        private static Type ThisControl = typeof(AgileTabControl);
        private string Template64 = "Base64 encoded template removed for clarity"
#endif


#if DEBUG
        public AgileTabControlDesigner() { InitializeComponent(); }
#else
        public AgileTabControl()
        {
            string decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(Template64));
            System.IO.StringReader sr = new System.IO.StringReader(decoded);
            System.Xml.XmlReader xr = System.Xml.XmlReader.Create(sr);
            ControlTemplate ct = (ControlTemplate)System.Windows.Markup.XamlReader.Load(xr);

            DefaultStyleKey = ThisControl;
            Template = ct;
        }
#endif

        public Thickness MarginHeaderPanel 
        {
            get { return (Thickness)GetValue(MarginHeaderPanelProperty); } 
            set { SetValue(MarginHeaderPanelProperty, value); } 
        }
        public static readonly DependencyProperty MarginHeaderPanelProperty =
            DependencyProperty.Register("MarginHeaderPanel", typeof(Thickness), ThisControl, new PropertyMetadata(new Thickness(0)));

        public CornerRadius BorderRadius 
        { 
            get { return (CornerRadius)GetValue(BorderRadiusProperty); } 
            set { SetValue(BorderRadiusProperty, value); }
        }
        public static readonly DependencyProperty BorderRadiusProperty =
            DependencyProperty.Register("BorderRadius", typeof(CornerRadius), ThisControl, new PropertyMetadata(new CornerRadius(0)));

        public object TabBlankSpaceContent 
        { 
            get { return (object)GetValue(TabBlankSpaceContentProperty); } 
            set { SetValue(TabBlankSpaceContentProperty, value); } 
        }
        public static readonly DependencyProperty TabBlankSpaceContentProperty =
            DependencyProperty.Register("TabBlankSpaceContent", typeof(object), ThisControl, new PropertyMetadata());
    }
}

프라이머리 어플리케이션에서 사용하는 "release" 제어 DLL을 작성하기 전에 기억해야 할 중요한 것은 base64 부호화 스트링을 제어 템플릿의 최신 및 최대 버전으로 갱신하는 것입니다.이는 릴리스 빌드가 원래 Xaml에서 완전히 분리되고 인코딩된 빌드에 전적으로 의존하기 때문입니다.

GitHub에는 위의 컨트롤과 이와 유사한 컨트롤이 있습니다.이 라이브러리는 표준 컨트롤이 드러내지 않는 스타일을 만들고 싶은 많은 것들을 "잠금 해제"하기 위한 것입니다.그리고 존재하지 않는 기능을 추가합니다.예를 들어 위의 TabControl에는 탭 헤더의 "미사용" 영역을 사용하기 위한 추가 컨텐츠 속성이 있습니다.

중요사항:

  • 하면 기본 되지만, 이 이 방법을 수 .BasedOn="{StaticResource {x:Type TabControl}}"메카니즘.
  • 주목할 만한 메모리 누설이 없는지, 또 이에 대해 의견이 있으시면 댓글로 알려주시기 바랍니다.

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

반응형