NAMED içeriğine sahip bir WPF UserControl nasıl oluşturulur


102

Sürekli aynı şekilde yeniden kullanılan, ekli komutlar ve mantık içeren bir dizi kontrolüm var. Tüm ortak kontrolleri ve mantığı tutan bir kullanıcı kontrolü oluşturmaya karar verdim.

Bununla birlikte, adlandırılabilen içeriği tutabilmek için kontrole de ihtiyacım var. Aşağıdakileri denedim:

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

Ancak, kullanıcı kontrolünün içine yerleştirilen herhangi bir içeriğin adlandırılamayacağı görülüyor. Örneğin, kontrolü şu şekilde kullanırsam:

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

Aşağıdaki hatayı alıyorum:

"Button" öğesinde "buttonName" Ad öznitelik değeri ayarlanamıyor. 'Düğme', başka bir kapsamda tanımlandığında zaten kayıtlı bir adı olan 'UserControl1' öğesinin kapsamındadır.

ButtonName'i kaldırırsam, derlenir, ancak içeriği adlandırabilmem gerekir. Bunu nasıl başarabilirim?


Bu bir tesadüf. Ben de tam bu soruyu soracaktım! Bende de aynı sorun var. Ortak UI modelini bir UserControl olarak hesaba katmak, ancak adıyla içerik kullanıcı arayüzüne başvurmak istemek.
mackenir

3
Bu adam , kendi özel kontrolünün XAML dosyasından kurtulmayı ve özel kontrolün kullanıcı arayüzünü programlı olarak oluşturmayı içeren bir çözüm buldu . Bu blog yazısı konu hakkında söylenecek daha çok şey var.
mackenir

2
Neden ResourceDictionary yolunu kullanmıyorsunuz? DataTemplate içindeki tanımlayın. Veya kontrolü devralmak için BasedOn anahtar sözcüğünü kullanın. WPF'de arka planda kodlama kullanıcı arabirimi yapmadan önce izleyeceğim bazı yollar ...
WPF'de Louis Kottmann

Yanıtlar:


46

Cevap, bunu yapmak için bir UserControl kullanmamaktır.

ContentControl'ü genişleten bir sınıf oluşturun

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

sonra içeriği belirtmek için bir stil kullanın

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

ve son olarak - onu kullanın

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

2
Bunu aslında en rahat çözüm olarak buldum çünkü Kontrol Şablonunu, tasarımcıyı kullanarak sıradan bir UserControl'de detaylandırabilir ve onu ilişkili kontrol şablonuyla bir stile dönüştürebilirsiniz.
Oliver Weichhold

5
Hmm, sizin için çalışması biraz garip çünkü bu yaklaşımı uygulamaya çalıştım ve benim durumumda hala bu rezil hatayı alıyorum.
greenoldman

8
@greenoldman @Badiboy Neden senin için işe yaramadığını biliyorum. muhtemelen var olan bir kodu UserControlmiras almak için değiştirmişsinizdir ContentControl. Çözmek için yeni Sınıf ekleyin ( CS ile XAML değil ). Ve sonra (umarım) işe yarayacak. İsterseniz küçük bir VS2010 çözümü
itsho

1
Yıllar sonra ve keşke buna tekrar oy verebilseydim :)
Drew Noakes

2
sanırım HeadingContainer ve MyFunkyContainerolması gerekiyor MyFunkyControlmu?
Martin Schneider

20

Görünüşe göre XAML kullanıldığında bu mümkün değil. İhtiyacım olan tüm kontrollere sahip olduğumda, özel kontroller bir aşırılık gibi görünüyor, ancak bunları küçük bir mantıkla birlikte gruplamam ve adlandırılmış içeriğe izin vermem gerekiyor.

Mackenir'in önerdiği gibi JD'nin blogundaki çözüm, en iyi uzlaşmaya sahip gibi görünüyor. JD'nin çözümünü, denetimlerin XAML'de hala tanımlanmasına izin verecek şekilde genişletmenin bir yolu aşağıdaki gibi olabilir:

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

Yukarıdaki örneğimde, XAML kullanan normal kullanıcı kontrolleri gibi tanımlanan UserControlDefinedInXAML adında bir kullanıcı kontrolü oluşturdum. UserControlDefinedInXAML'imde, adlandırılmış içeriğimin görünmesini istediğim aStackPanel adında bir StackPanel var.


Bu içeriği yeniden ebeveynlik yapma mekanizmasını kullanırken veri bağlama sorunları yaşadığımı buluyorum. Veri bağlama doğru ayarlanmış gibi görünüyor, ancak denetimlerin veri kaynağından ilk doldurulması düzgün çalışmıyor. Sorunun, içerik sunan kişinin doğrudan alt öğesi olmayan kontrollerle sınırlı olduğunu düşünüyorum.
mackenir

O zamandan beri bunu deneme şansım olmadı, ancak şu ana kadar UserControlDefinedInXAML'de (yukarıdaki örnekten) veya ContentPresenter'a eklenen kontrollerde tanımlanan kontrollere veri bağlama konusunda herhangi bir sorun yaşamadım. Yine de yalnızca kod aracılığıyla veri bağlama yapıyorum (XAML değil - bunun bir fark yaratıp yaratmadığından emin değilim).
Ryan

Görünüşe göre bir fark yaratıyor. Açıkladığınız durumda veri bağlamalarım için XAML kullanmayı denedim ve çalışmıyor. Ama onu kod olarak ayarlarsam işe yarıyor!
Ryan

3

Kullandığım başka bir alternatif de Name özelliği Loadedolayda .

Benim durumumda, arka planda kod oluşturmak istemediğim oldukça karmaşık bir kontrolüm vardı ve belirli bir davranış için belirli bir adla isteğe bağlı bir kontrol aradı ve fark ettiğimden beri adı bir DataTemplateBen de bunu yapabileceğimi düşündüm Loaded.

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

2
Bunu yaparsanız, arkadaki kodda bağlamaları ayarlamadığınız sürece adı kullanan bağlamalar çalışmayacaktır.
Kule

3

Bazen öğeye C # 'dan başvurmanız gerekebilir. Kullanım durumuna bağlı olarak, daha sonra bir x:Uidyerine bir belirleyebilir x:Nameve WPF'deki Uid'ine göre Get nesnesi gibi bir Uid bulucu yöntemini çağırarak öğelere erişebilirsiniz .


1

Bu yardımcıyı, kullanıcı kontrolünde set adı için kullanabilirsiniz:

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

ve Pencere veya Arkasındaki Kontrol Kodunuz, Mülkiyete göre kontrol etmenizi sağlar:

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

    }

    public Button BtnOK { get; set; }
}

pencereniz 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, Kontrolü Özellik olarak ayarlamak için kontrol adınızı ve Sınıf adınızı alın.


0

Almam gereken her öğe için fazladan bir mülk oluşturmayı seçtim:

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

Bu, XAML'deki alt öğelere erişmemi sağlıyor:

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

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

Arkasındaki kod:

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

Gerçek çözüm, Microsoft'un bu sorunu ve kırık görsel ağaçları olan tüm diğerlerini vb. Düzeltmesi olacaktır. Varsayımsal olarak konuşursak.


0

Yine başka bir geçici çözüm: Öğeye RelativeSource olarak başvurma .


0

Bir grup adlandırılmış denetimi içine yerleştirirken de TabControl kullanarak aynı sorunu yaşadım.

Çözümüm, bir sekme sayfasında gösterilecek tüm kontrollerimi içeren bir kontrol şablonu kullanmaktı. Şablonun içinde Name özelliğini kullanabilir ve aynı zamanda en azından aynı şablonun içindeki diğer kontrollerden adlandırılmış kontrolün özelliklerine veri bağlayabilirsiniz.

TabItem Denetiminin İçeriği olarak, basit bir Denetim kullanın ve Denetim Şablonunu buna göre ayarlayın:

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

Şablonun içindeki adlandırılmış kontrollere arkanızdaki koddan erişmek, görsel ağacı kullanmanız gerekir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.