วิธีสร้าง WPF UserControl ด้วยเนื้อหา NAMED


102

ฉันมีชุดการควบคุมพร้อมคำสั่งและตรรกะที่แนบมาซึ่งใช้ซ้ำในลักษณะเดียวกันอยู่ตลอดเวลา ฉันตัดสินใจที่จะสร้างการควบคุมผู้ใช้ที่มีการควบคุมและตรรกะทั่วไปทั้งหมด

อย่างไรก็ตามฉันต้องการการควบคุมเพื่อให้สามารถเก็บเนื้อหาที่สามารถตั้งชื่อได้ ฉันลองทำสิ่งต่อไปนี้:

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

ฉันได้รับข้อผิดพลาดต่อไปนี้:

ไม่สามารถตั้งค่าแอตทริบิวต์ Name "buttonName" ในองค์ประกอบ "Button" 'ปุ่ม' อยู่ภายใต้ขอบเขตขององค์ประกอบ 'UserControl1' ซึ่งมีชื่อที่ลงทะเบียนไว้แล้วเมื่อถูกกำหนดในขอบเขตอื่น

ถ้าฉันลบ buttonName ออกมันจะคอมไพล์ แต่ฉันต้องสามารถตั้งชื่อเนื้อหาได้ ฉันจะบรรลุเป้าหมายนี้ได้อย่างไร?


นี่เป็นเรื่องบังเอิญ ฉันกำลังจะถามคำถามนี้! ผมมีปัญหาเดียวกัน. แยกแยะรูปแบบ UI ทั่วไปลงใน UserControl แต่ต้องการอ้างถึง UI เนื้อหาตามชื่อ
mackenir

3
ผู้ชายคนนี้พบวิธีแก้ปัญหาเกี่ยวกับการกำจัดไฟล์ XAML ของตัวควบคุมที่กำหนดเองและสร้าง UI ของคอนโทรลแบบกำหนดเองโดยใช้โปรแกรม โพสต์บล็อกนี้มีเนื้อหาเพิ่มเติมเกี่ยวกับเรื่องนี้
mackenir

2
ทำไมคุณไม่ใช้วิธี ResourceDictionary กำหนด DataTemplate ในนั้น หรือใช้คีย์เวิร์ด BasedOn เพื่อสืบทอดคอนโทรล เพียงบางเส้นทางที่ฉันทำตามก่อนที่จะทำ UI ด้านหลังโค้ดใน WPF ...
Louis 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
ฉันพบว่านี่เป็นวิธีแก้ปัญหาที่สะดวกสบายที่สุดเนื่องจากคุณสามารถสร้าง ControlTemplate ใน UserControl ธรรมดาโดยใช้ตัวออกแบบและเปลี่ยนเป็นสไตล์ที่มีเทมเพลตการควบคุมที่เกี่ยวข้อง
Oliver Weichhold

5
อืมมันค่อนข้างแปลกสำหรับคุณเพราะฉันพยายามใช้แนวทางนี้และในกรณีของฉันฉันยังคงได้รับข้อผิดพลาดที่น่าอับอายนี้
greenoldman

8
@greenoldman @Badiboy ฉันคิดว่าฉันรู้ว่าทำไมมันถึงไม่ได้ผลสำหรับคุณ คุณอาจเพิ่งเปลี่ยนรหัสที่มีอยู่จากUserControlการสืบทอดContentControlการสืบทอดในการแก้ปัญหาให้เพิ่ม Class ใหม่ ( ไม่ใช่ XAML กับ CS) แล้วมันจะทำงาน (หวังว่า) ถ้าคุณต้องการฉันได้สร้างโซลูชัน VS2010
itho

1
หลายปีต่อมาและฉันหวังว่าฉันจะโหวตให้คะแนนอีกครั้ง :)
Drew Noakes

2
ฉันเดาHeadingContainerและMyFunkyContainerตั้งใจจะเป็นMyFunkyControl?!
มาร์ตินชไนเดอร์

20

ดูเหมือนว่าจะไม่สามารถทำได้เมื่อใช้ XAML การควบคุมแบบกำหนดเองดูเหมือนจะเกินความจำเป็นเมื่อฉันมีการควบคุมทั้งหมดที่ต้องการ แต่เพียงแค่ต้องจัดกลุ่มเข้าด้วยกันด้วยตรรกะเล็กน้อยและอนุญาตให้มีเนื้อหาที่มีชื่อ

วิธีแก้ปัญหาในบล็อกของ JDตามที่ mackenir แนะนำดูเหมือนจะมีการประนีประนอมที่ดีที่สุด วิธีขยายโซลูชันของ JD เพื่อให้การควบคุมยังคงถูกกำหนดใน XAML อาจเป็นดังนี้:

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

ในตัวอย่างของฉันด้านบนฉันได้สร้างการควบคุมผู้ใช้ที่เรียกว่า UserControlDefinedInXAML ซึ่งกำหนดเหมือนกับการควบคุมผู้ใช้ทั่วไปโดยใช้ XAML ใน UserControlDefinedInXAML ของฉันฉันมี StackPanel ที่เรียกว่า aStackPanel ซึ่งฉันต้องการให้เนื้อหาที่ตั้งชื่อของฉันปรากฏขึ้น


ฉันพบว่าฉันมีปัญหาในการผูกข้อมูลเมื่อใช้กลไกนี้ในการเลี้ยงดูเนื้อหาใหม่ ดูเหมือนว่าการผูกข้อมูลจะถูกตั้งค่าอย่างถูกต้อง แต่การเติมข้อมูลครั้งแรกของตัวควบคุมจากแหล่งข้อมูลทำงานไม่ถูกต้อง ฉันคิดว่าปัญหา จำกัด อยู่ที่การควบคุมที่ไม่ใช่ลูกโดยตรงของผู้นำเสนอเนื้อหา
mackenir

ไม่ได้มีโอกาสทดลองกับสิ่งนี้ตั้งแต่นั้นมา แต่ฉันไม่มีปัญหาในการเชื่อมโยงฐานข้อมูลกับตัวควบคุมที่กำหนดไว้ใน UserControlDefinedInXAML (จากตัวอย่างด้านบน) หรือตัวควบคุมที่เพิ่มไปยัง ContentPresenter จนถึงตอนนี้ ฉันใช้การค้นหาฐานข้อมูลผ่านรหัสเท่านั้น (ไม่ใช่ XAML - ไม่แน่ใจว่าจะสร้างความแตกต่างหรือไม่)
Ryan

ดูเหมือนว่าจะสร้างความแตกต่าง ฉันเพิ่งลองใช้ XAML สำหรับการเชื่อมโยงข้อมูลในกรณีที่คุณอธิบายไว้และไม่ได้ผล แต่ถ้าตั้งเป็นรหัสก็ใช้ได้!
Ryan

3

อีกทางเลือกหนึ่งที่ฉันใช้คือตั้งค่าNameคุณสมบัติในLoadedงาน

ในกรณีของฉันฉันมีการควบคุมที่ค่อนข้างซับซ้อนซึ่งฉันไม่ต้องการสร้างในโค้ดข้างหลังและมันมองหาตัวควบคุมเสริมที่มีชื่อเฉพาะสำหรับพฤติกรรมบางอย่างและตั้งแต่ฉันสังเกตว่าฉันสามารถตั้งชื่อในDataTemplateฉันคิดว่าฉันสามารถทำได้ในLoadedงานด้วย

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

2
หากคุณทำเช่นนี้การผูกโดยใช้ชื่อจะไม่ทำงาน ... เว้นแต่คุณจะตั้งค่าการผูกในโค้ดไว้ข้างหลัง
ทาวเวอร์

3

บางครั้งคุณอาจต้องอ้างอิงองค์ประกอบจาก C # ทั้งนี้ขึ้นอยู่กับกรณีการใช้งานแล้วคุณสามารถตั้งค่าx:Uidแทนx:Nameและการเข้าถึงองค์ประกอบโดยการเรียกวิธีการค้นหา Uid เช่นวัตถุที่ได้รับโดย Uid ใน WPF


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 รับชื่อควบคุมและชื่อคลาสของคุณสำหรับตั้งค่า Control to Property


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

ฉันมีปัญหาเดียวกันกับการใช้ TabControl เมื่อวางส่วนควบคุมที่มีชื่อไว้ในไฟล์.

วิธีแก้ปัญหาของฉันคือใช้เทมเพลตการควบคุมซึ่งมีตัวควบคุมทั้งหมดของฉันที่จะแสดงในหน้าแท็บ ภายในเทมเพลตคุณสามารถใช้คุณสมบัติ Name และการเชื่อมโยงข้อมูลกับคุณสมบัติของตัวควบคุมที่มีชื่อจากตัวควบคุมอื่น ๆ อย่างน้อยภายในเทมเพลตเดียวกัน

ในฐานะที่เป็นเนื้อหาของ TabItem Control ให้ใช้ Control แบบธรรมดาและตั้งค่า ControlTemplate ตาม:

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

การเข้าถึงการควบคุมที่มีชื่อภายในเทมเพลตจากโค้ดด้านหลังคุณจะต้องใช้แผนภูมิภาพ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.