Cara membuat UserControl WPF dengan konten NAMED

100

Saya memiliki satu set kontrol dengan perintah dan logika terlampir yang terus digunakan kembali dengan cara yang sama. Saya memutuskan untuk membuat kontrol pengguna yang menampung semua kontrol dan logika umum.

Namun saya juga membutuhkan kontrol untuk dapat menyimpan konten yang dapat diberi nama. Saya mencoba yang berikut ini:

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

Namun tampaknya konten apa pun yang ditempatkan di dalam kendali pengguna tidak dapat dinamai. Misalnya jika saya menggunakan kontrol dengan cara berikut:

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

Saya menerima kesalahan berikut:

Tidak dapat menyetel nilai atribut Nama 'buttonName' pada elemen 'Tombol'. 'Tombol' berada di bawah cakupan elemen 'UserControl1', yang sudah memiliki nama yang terdaftar saat didefinisikan dalam cakupan lain.

Jika saya menghapus buttonName, maka akan terkompilasi, namun saya harus dapat menamai konten tersebut. Bagaimana saya bisa mencapai ini?

Ryan
sumber
Ini kebetulan. Saya baru saja akan menanyakan pertanyaan ini! Saya memiliki masalah yang sama. Memfaktorkan pola UI umum ke dalam UserControl, tetapi ingin merujuk ke UI konten menurut namanya.
mackenir
3
Orang ini menemukan solusi yang melibatkan penghapusan file XAML kontrol kustomnya, dan membangun UI kontrol kustom secara terprogram. Entri blog ini membahas lebih banyak tentang masalah ini.
mackenir
2
Mengapa Anda tidak menggunakan cara ResourceDictionary? Tentukan DataTemplate di dalamnya. Atau gunakan kata kunci BasedOn untuk mewarisi kontrol. Hanya beberapa jalur yang akan saya ikuti sebelum melakukan UI di belakang kode di WPF ...
Louis Kottmann

Jawaban:

46

Jawabannya adalah tidak menggunakan UserControl untuk melakukannya.

Buat kelas yang memperluas 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; }
}

lalu gunakan gaya untuk menentukan isinya

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

dan terakhir - gunakan

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>
Sybrand
sumber
2
Saya menemukan ini benar-benar solusi yang paling nyaman karena Anda dapat menyempurnakan ControlTemplate dalam UserControl biasa menggunakan desainer dan mengubahnya menjadi gaya dengan template kontrol terkait.
Oliver Weichhold
5
Hmm, agak aneh ini berhasil untuk Anda, karena saya mencoba menerapkan pendekatan ini dan dalam kasus saya, saya masih mendapatkan kesalahan terkenal ini.
greenoldman
8
@greenoldman @Badiboy Saya rasa saya tahu mengapa itu tidak berhasil untuk Anda. Anda mungkin baru saja mengubah kode yang ada dari UserControlmenjadi mewarisi ContentControl. Untuk mengatasinya, cukup tambahkan Kelas baru ( bukan XAML dengan CS). Dan kemudian itu akan (semoga) berhasil. jika Anda suka, saya telah membuat solusi VS2010
itsho
1
Bertahun-tahun kemudian dan saya berharap saya dapat meningkatkan ini lagi :)
Drew Noakes
2
Saya kira HeadingContainerdan MyFunkyContainerdimaksudkan untuk menjadi MyFunkyControl?!
Martin Schneider
20

Sepertinya ini tidak mungkin ketika XAML digunakan. Kontrol khusus tampaknya berlebihan ketika saya benar-benar memiliki semua kontrol yang saya butuhkan, tetapi hanya perlu mengelompokkannya bersama dengan sedikit logika dan mengizinkan konten bernama.

Solusi di blog JD seperti yang disarankan mackenir, tampaknya memiliki kompromi terbaik. Cara untuk memperluas solusi JD untuk memungkinkan kontrol tetap didefinisikan di XAML adalah sebagai berikut:

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

Dalam contoh saya di atas, saya telah membuat kontrol pengguna yang disebut UserControlDefinedInXAML yang didefinisikan seperti kontrol pengguna biasa menggunakan XAML. Di UserControlDefinedInXAML saya, saya memiliki StackPanel yang disebut aStackPanel di mana saya ingin konten bernama saya muncul.

Ryan
sumber
Saya telah menemukan bahwa saya mendapatkan masalah pengikatan data saat menggunakan mekanisme mengasuh ulang konten ini. Data-binding tampaknya disiapkan dengan benar, tetapi pengisian awal kontrol dari sumber data tidak berfungsi dengan benar. Saya rasa masalahnya terbatas pada kontrol yang bukan merupakan turunan langsung dari penyaji konten.
mackenir
Tidak memiliki kesempatan untuk bereksperimen dengan ini sejak itu, tetapi saya tidak memiliki masalah terkait databinding baik ke kontrol yang ditentukan dalam UserControlDefinedInXAML (dari contoh di atas) atau kontrol yang ditambahkan ke ContentPresenter sejauh ini. Saya telah menyatukan data melalui kode saja (bukan XAML - tidak yakin apakah itu membuat perbedaan).
Ryan
Sepertinya itu TIDAK membuat perbedaan. Saya baru saja mencoba menggunakan XAML untuk pengikatan data saya dalam kasus yang Anda jelaskan dan tidak berhasil. Tetapi jika saya mengaturnya dalam kode, itu berhasil!
Ryan
3

Alternatif lain yang saya gunakan adalah mengatur Nameproperti di Loadedacara tersebut.

Dalam kasus saya, saya memiliki kontrol yang agak rumit yang tidak ingin saya buat di belakang kode, dan mencari kontrol opsional dengan nama tertentu untuk perilaku tertentu, dan karena saya perhatikan saya dapat menetapkan nama di DataTemplateSaya pikir saya bisa melakukannya di Loadedacara itu juga.

private void Button_Loaded(object sender, RoutedEventArgs e)
{
    Button b = sender as Button;
    b.Name = "buttonName";
}
Rachel
sumber
2
Jika Anda melakukan ini, maka binding menggunakan nama tidak akan berfungsi ... kecuali Anda menyetel binding dalam kode di belakang.
Menara
3

Terkadang Anda mungkin hanya perlu mereferensikan elemen dari C #. Bergantung pada kasus penggunaan, Anda kemudian dapat menyetel dan x:Uidbukannya x:Namedan mengakses elemen dengan memanggil metode pencari Uid seperti Get object by-nya Uid di WPF .

Dani
sumber
1

Anda dapat menggunakan helper ini untuk mengatur nama di dalam kontrol pengguna:

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

dan Window atau Control Code Behind Anda mengatur kontrol Anda berdasarkan Properti:

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

    }

    public Button BtnOK { get; set; }
}

jendela Anda 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 mendapatkan nama kontrol dan nama Kelas Anda untuk mengatur Kontrol ke Properti.

Ali Yousefi
sumber
0

Saya telah memilih untuk membuat properti tambahan untuk setiap elemen yang saya butuhkan:

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

Ini memungkinkan saya untuk mengakses elemen anak di XAML:

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

Kode di belakang:

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

Solusi sebenarnya adalah bagi Microsoft untuk memperbaiki masalah ini, serta semua masalah lainnya dengan pohon visual yang rusak, dll. Secara hipotetis.

15ee8f99-57ff-4f92-890c-b56153
sumber
0

Solusi lain: rujuk elemen sebagai RelativeSource .

voccoeisuoi
sumber
0

Saya memiliki masalah yang sama menggunakan TabControl ketika menempatkan sekelompok kontrol bernama ke.

Solusi saya adalah menggunakan template kontrol yang berisi semua kontrol saya untuk ditampilkan di halaman tab. Di dalam template Anda bisa menggunakan properti Name dan juga data mengikat ke properti kontrol bernama dari kontrol lain setidaknya di dalam template yang sama.

Sebagai Content of the TabItem Control, gunakan Control sederhana dan setel ControlTemplate sesuai:

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

Mengakses kontrol bernama di dalam templat dari kode di belakang Anda perlu menggunakan pohon visual.

David Wellna
sumber