Cách tạo UserControl WPF với nội dung NAMED


100

Tôi có một bộ điều khiển với các lệnh và logic được đính kèm liên tục được sử dụng lại theo cách tương tự. Tôi quyết định tạo điều khiển người dùng chứa tất cả các điều khiển và logic thông thường.

Tuy nhiên tôi cũng cần kiểm soát để có thể giữ nội dung có thể được đặt tên. Tôi đã thử những cách sau:

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

Tuy nhiên, có vẻ như bất kỳ nội dung nào được đặt bên trong sự kiểm soát của người dùng đều không thể được đặt tên. Ví dụ: nếu tôi sử dụng điều khiển theo cách sau:

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

Tôi nhận được lỗi sau:

Không thể đặt giá trị thuộc tính Tên 'buttonName' trên phần tử 'Button'. 'Nút' nằm trong phạm vi của phần tử 'UserControl1', phần tử này đã có tên được đăng ký khi nó được xác định trong phạm vi khác.

Nếu tôi loại bỏ buttonName, thì nó sẽ biên dịch, tuy nhiên tôi cần có thể đặt tên cho nội dung. Làm thế nào tôi có thể đạt được điều này?


Đây là một sự trùng hợp ngẫu nhiên. Tôi vừa định hỏi câu hỏi này! Tôi có cùng một vấn đề. Bao gồm mẫu giao diện người dùng phổ biến thành UserControl, nhưng muốn tham chiếu đến giao diện người dùng nội dung theo tên.
mackenir

3
Anh chàng này đã tìm thấy một giải pháp liên quan đến việc loại bỏ tệp XAML của điều khiển tùy chỉnh của mình và xây dựng giao diện người dùng của điều khiển tùy chỉnh theo lập trình. Bài đăng trên blog này có nhiều điều để nói về chủ đề này.
mackenir

2
Tại sao bạn không sử dụng ResourceDictionary theo cách? Xác định DataTemplate trong đó. Hoặc sử dụng từ khóa BasedOn để kế thừa điều khiển. Chỉ cần một số đường dẫn Tôi muốn làm theo trước khi làm code-behind UI trong WPF ...
Louis Kottmann

Câu trả lời:


46

Câu trả lời là không sử dụng UserControl để làm điều đó.

Tạo một lớp mở rộng 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; }
}

sau đó sử dụng một kiểu để chỉ định nội dung

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

và cuối cùng - sử dụng nó

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

2
Tôi thấy đây thực sự là giải pháp thoải mái nhất vì bạn có thể tạo ra ControlTemplate trong một UserControl thông thường bằng cách sử dụng trình thiết kế và biến nó thành một kiểu với mẫu điều khiển được liên kết.
Oliver Weichhold

5
Hmm, nó có hiệu quả với bạn hơi lạ vì tôi đã cố gắng áp dụng cách tiếp cận này và trong trường hợp của tôi, tôi vẫn gặp phải lỗi khét tiếng này.
greenoldman

8
@greenoldman @Badiboy Tôi nghĩ rằng tôi biết tại sao nó không hiệu quả với bạn. bạn có thể vừa thay đổi một mã hiện có từ UserControlđể kế thừa ContentControl. Để giải quyết, chỉ cần thêm Class mới ( không phải XAML với CS). Và sau đó nó sẽ (hy vọng) hoạt động. nếu bạn thích, tôi đã tạo một giải pháp VS2010
itho

1
Nhiều năm sau và tôi ước gì có thể upvote này một lần nữa :)
Drew Noakes

2
Tôi đoán HeadingContainerMyFunkyContainercó ý định như MyFunkyControlvậy ?!
Martin Schneider

20

Có vẻ như điều này là không thể khi XAML được sử dụng. Các điều khiển tùy chỉnh dường như là một việc làm quá mức cần thiết khi tôi thực sự có tất cả các điều khiển mình cần, nhưng chỉ cần nhóm chúng lại với nhau bằng một chút logic nhỏ và cho phép nội dung được đặt tên.

Giải pháp trên blog của JD như mackenir gợi ý, dường như có sự thỏa hiệp tốt nhất. Một cách để mở rộng giải pháp của JD để cho phép các điều khiển vẫn được xác định trong XAML có thể như sau:

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

Trong ví dụ của tôi ở trên, tôi đã tạo một điều khiển người dùng được gọi là UserControlDefinedInXAML được định nghĩa giống như bất kỳ điều khiển người dùng bình thường nào sử dụng XAML. Trong UserControlDefinedInXAML, tôi có một StackPanel được gọi là aStackPanel trong đó tôi muốn nội dung được đặt tên của mình xuất hiện.


Tôi nhận thấy rằng mình gặp phải các vấn đề liên kết dữ liệu khi sử dụng cơ chế tái tạo nội dung này. Liên kết dữ liệu dường như được thiết lập chính xác, nhưng việc điền ban đầu của các điều khiển từ nguồn dữ liệu không hoạt động bình thường. Tôi nghĩ rằng vấn đề được giới hạn ở các kiểm soát không phải là con trực tiếp của người trình bày nội dung.
mackenir

Kể từ đó, tôi không có cơ hội thử nghiệm với điều này, nhưng tôi không gặp bất kỳ vấn đề nào khi liên kết dữ liệu cho các điều khiển được xác định trong UserControlDefinedInXAML (từ ví dụ trên) hoặc các điều khiển được thêm vào ContentPresenter cho đến nay. Mặc dù vậy, tôi chỉ sử dụng dữ liệu thông qua mã (không phải XAML - không chắc liệu điều đó có tạo ra sự khác biệt hay không).
Ryan

Có vẻ như nó KHÔNG tạo ra sự khác biệt. Tôi vừa thử sử dụng XAML cho các ràng buộc dữ liệu của mình trong trường hợp bạn mô tả và nó không hoạt động. Nhưng nếu tôi đặt nó trong mã, nó hoạt động!
Ryan

3

Một giải pháp thay thế khác mà tôi đã sử dụng là chỉ đặt thuộc Nametính trong Loadedsự kiện.

Trong trường hợp của tôi, tôi có một điều khiển khá phức tạp mà tôi không muốn tạo trong mã phía sau và nó tìm kiếm một điều khiển tùy chọn có tên cụ thể cho một số hành vi nhất định và vì tôi nhận thấy rằng tôi có thể đặt tên trong DataTemplateTôi nghĩ rằng tôi có thể làm điều đó trong Loadedsự kiện này.

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

2
Nếu bạn làm điều này, thì các ràng buộc sử dụng tên sẽ không hoạt động ... trừ khi bạn đặt các ràng buộc trong mã phía sau.
Tower

3

Đôi khi bạn có thể chỉ cần tham chiếu phần tử từ C #. Tùy thuộc vào trường hợp sử dụng, sau đó bạn có thể đặt một x:Uidthay vì một x:Namevà truy cập vào các phần tử bằng cách gọi một phương thức tìm Uid như Lấy đối tượng bằng Uid của nó trong WPF .


1

Bạn có thể sử dụng trình trợ giúp này để đặt tên bên trong điều khiển người dùng:

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

và Cửa sổ hoặc Mã điều khiển Phía sau đặt bạn kiểm soát theo Thuộc tính:

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

    }

    public Button BtnOK { get; set; }
}

window xaml của bạn:

    <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 lấy tên điều khiển của bạn và tên Lớp của bạn để đặt Điều khiển thành Thuộc tính.


0

Tôi đã chọn tạo một thuộc tính bổ sung cho mỗi phần tử mà tôi cần lấy:

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

Điều này cho phép tôi truy cập các phần tử con trong XAML:

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

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

Mã ẩn:

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

Giải pháp thực sự sẽ dành cho Microsoft để khắc phục sự cố này, cũng như tất cả những người khác có cây thị giác bị hỏng, v.v. Nói một cách giả thuyết.


0

Tuy nhiên, một giải pháp khác: tham chiếu phần tử là RelativeSource .


0

Tôi đã gặp vấn đề tương tự khi sử dụng TabControl khi đặt một loạt các điều khiển được đặt tên vào.

Cách giải quyết của tôi là sử dụng mẫu điều khiển chứa tất cả các điều khiển của tôi sẽ được hiển thị trong trang tab. Bên trong mẫu, bạn có thể sử dụng thuộc tính Tên và cả dữ liệu liên kết với các thuộc tính của điều khiển được đặt tên từ các điều khiển khác ít nhất là bên trong cùng một mẫu.

Là Nội dung của Điều khiển TabItem, hãy sử dụng Điều khiển đơn giản và đặt ControlTemplate cho phù hợp:

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

Truy cập vào những điều khiển được đặt tên bên trong mẫu từ mã phía sau, bạn sẽ cần sử dụng cây trực quan.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.