2009-04-15 23 views
73

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

Tuy nhiên tôi cũng cần sự kiểm soát để có thể giữ nội dung có thể được đặt tên. Tôi đã thử các 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 điều khiển 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:

Cannot set Name attribute value 'buttonName' on element 'Button'. 'Button' is under the scope of element 'UserControl1', which already had a name registered when it was defined in another scope.

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

+0

Đây là 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 thanh toán mô hình giao diện người dùng thông thường 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

+1

Tại sao bạn không sử dụng cách ResourceDictionary? 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ố con đường tôi muốn làm theo trước khi làm code-phía sau giao diện người dùng trong WPF ... –

+2

Anh chàng này tìm thấy một [giải pháp] (http://blog.bluecog.co.nz/archives/2007/08/27/wpf-cannot -set-name-attribute /) liên quan đến việc loại bỏ tệp XAML của kiểm soát tùy chỉnh của mình và xây dựng giao diện người dùng của kiểm soát tùy chỉnh theo lập trình. [Bài đăng trên blog] này (http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!2130.entry?wa=wsignin1.0&sa=752255111) có nhiều điều để nói về chủ đề này. – mackenir

Trả lời

16

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

Các giải pháp trên JD's blog như mackenir cho thấy, dường như có sự thỏa hiệp tốt nhất. Một cách để mở rộng giải pháp JD để cho phép điều khiển để vẫn được định nghĩa trong XAML có thể là 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 gọi UserControlDefinedInXAML đó là xác định như bất kỳ điều khiển người dùng thông thường sử dụng XAML. Trong UserControlDefinedInXAML của tôi, 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.

+0

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

+0

Không có cơ hội để thử nghiệm với điều này từ đó, nhưng tôi đã không có bất kỳ vấn đề liên kết dữ liệu hoặc là các điều khiển được định nghĩa trong UserControlDefinedInXAML (từ trên cao chẳng hạn) hoặc các điều khiển bổ sung vào ContentPresenter cho đến nay. Tôi đã được databinding qua mã chỉ dù (không XAML - không chắc chắn nếu mà làm cho một sự khác biệt). – Ryan

+0

Dường như nó KHÔNG tạo sự khác biệt. Tôi chỉ cố gắng sử dụng XAML cho các ràng buộc dữ liệu của tôi 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

31

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

Tạo một lớp mà kéo dài 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 phong cách để xác đị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 điều này thực sự là giải pháp thoải mái nhất kể từ khi bạn có thể vạch ra những ControlTemplate trong một UserControl bình thường bằng cách sử dụng thiết kế và biến nó thành một phong cách với mẫu kiểm soát liên quan. –

+1

Hmm, nó là một chút lạ nó làm việc cho bạn, bởi vì tôi đã cố gắng để áp dụng phương pháp này và trong trường hợp của tôi, tôi vẫn nhận được lỗi khét tiếng này. – greenoldman

+1

Cùng với tôi: không có hiệu lực. :( – Badiboy

3

Một thay thế tôi đã sử dụng là chỉ cần đặt Name bất động sản trong sự kiện Loaded.

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

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, sau đó bindings bằng cách sử dụng tên sẽ không hoạt động ... trừ khi bạn thiết lập các bindings trong mã phía sau. – Tower

0

tôi đã chọn để tạo thuộc tính bổ sung cho mỗi yếu tố tôi cần phải nhận được:

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 phần tử con trong XAML:

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

Đôi khi bạn có thể chỉ cần tham khảo phần tử từ C#. Tùy thuộc vào trường hợp sử dụng, bạn có thể đặt x:Uid thay vì số x:Name và truy cập các yếu tố bằng cách gọi phương thức trình tìm kiếm Uid như Get object by its Uid in WPF.

0

Bạn có thể sử dụng helper này cho biết tên thiết lập 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à Window của bạn hoặc Mã kiểm soát Đằng sau bộ bạn kiểm soát bằng tài sản:

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

    } 

    public Button BtnOK { get; set; } 
} 

cửa sổ 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 kiểm soát của bạn và Tên lớp của bạn để đặt Kiểm soát thành P roperty.

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

Mã đằng sau:

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

Các giải pháp thực tế sẽ cho Microsoft để khắc phục vấn đề này, cũng như tất cả những người khác với cây thị giác bị phá vỡ, vv theo giả thuyết nói.

0

Một cách giải quyết 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 nhóm 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 kiểm soát chứa tất cả các điều khiển của tôi được hiển thị trong trang tab. Bên trong khuôn mẫu, bạn có thể sử dụng thuộc tính Name và cũng 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 trong cùng một khuôn mẫu.

Như nội dung của Control TabItem, sử dụng một điều khiển đơn giản và thiết lập ControlTemplate cho phù hợp:

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

Tiếp cận những người có tên kiểm soát bên trong mẫu từ mã phía sau bạn sẽ cần phải sử dụng cây thị giác.

Các vấn đề liên quan