2009-06-22 15 views
25

Tôi đang phát triển ứng dụng LOB, nơi tôi sẽ cần nhiều cửa sổ hộp thoại (và hiển thị mọi thứ trong một cửa sổ không phải là một tùy chọn/không giác quan).WPF: mẫu hoặc UserControl với 2 (hoặc nhiều hơn!) Người trình bày nội dung để hiển thị nội dung trong 'slots'

Tôi muốn có một điều khiển người dùng cho cửa sổ của tôi có thể xác định kiểu dáng, v.v., và sẽ có một số vị trí vị trí chèn nội dung - ví dụ: nội dung và các nút (để người dùng có thể cung cấp nội dung và tập hợp các nút bằng ICommands bị ràng buộc).

Tôi muốn có một cái gì đó như thế này (nhưng điều này không làm việc):

UserControl XAML:

<UserControl x:Class="TkMVVMContainersSample.Services.Common.GUI.DialogControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" 
    > 
    <DockPanel> 
     <DockPanel 
      LastChildFill="False" 
      HorizontalAlignment="Stretch" 
      DockPanel.Dock="Bottom"> 
      <ContentPresenter ContentSource="{Binding Buttons}"/> 
     </DockPanel> 
     <Border 
      Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" 
      Padding="8" 
      > 
      <ContentPresenter ContentSource="{Binding Controls}"/> 
     </Border> 
    </DockPanel> 
</UserControl> 

là một cái gì đó như thể này? Làm thế nào tôi nên nói với VS rằng kiểm soát của tôi cho thấy hai phần giữ chỗ nội dung để tôi có thể sử dụng nó như thế này?

<Window ... DataContext="MyViewModel"> 

    <gui:DialogControl> 
     <gui:DialogControl.Controls> 
      <!-- My dialog content - grid with textboxes etc... 
      inherits the Window's DC - DialogControl just passes it through --> 
     </gui:DialogControl.Controls> 
     <gui:DialogControl.Buttons> 
      <!-- My dialog's buttons with wiring, like 
      <Button Command="{Binding HelpCommand}">Help</Button> 
      <Button Command="{Binding CancelCommand}">Cancel</Button> 
      <Button Command="{Binding OKCommand}">OK</Button> 
      - they inherit DC from the Window, so the OKCommand binds to MyViewModel.OKCommand 
      --> 
     </gui:DialogControl.Buttons> 
    </gui:DialogControl> 

</Window> 

Hoặc có lẽ tôi có thể sử dụng một ControlTemplate cho một cửa sổ like here, nhưng sau đó một lần nữa: Window chỉ có một khe nội dung, do đó mẫu của nó sẽ có thể chỉ có một người dẫn chương trình, nhưng tôi cần hai (và nếu trong Trong trường hợp này, có thể sử dụng một số trường hợp khác, nơi một số vùng nội dung sẽ đến, chỉ cần nghĩ về một mẫu cho người dùng điều khiển sẽ cung cấp một tiêu đề, nội dung có cấu trúc, tên tác giả, hình ảnh. ..).

Cảm ơn bạn!

PS: Nếu tôi muốn chỉ có các nút cạnh nhau, làm cách nào tôi có thể đặt nhiều nút điều khiển (nút) cho StackPanel? ListBox có ItemsSource, nhưng StackPanel có không, và nó sở hữu Trẻ em là chỉ đọc - vì vậy điều này không làm việc (bên trong usercontrol):

<StackPanel 
    Orientation="Horizontal" 
    Children="{Binding Buttons}"/> 

EDIT: Tôi không muốn sử dụng ràng buộc, như tôi muốn gán một DataContext (ViewModel) cho toàn bộ cửa sổ (bằng View), và sau đó liên kết với các lệnh của nó từ các nút được chèn vào 'slots' điều khiển - vì vậy bất kỳ việc sử dụng ràng buộc nào trong cấu trúc phân cấp sẽ phá vỡ kế thừa của DC của View.

Đối với ý tưởng kế thừa từ HeaderedContentControl - có, trong trường hợp này nó sẽ hoạt động, nhưng nếu tôi muốn ba phần có thể thay thế thì sao? Làm cách nào để tạo "HeaderedAndFooteredContentControl" của riêng mình (hoặc, làm cách nào để triển khai HeaderedContentControl nếu tôi không có)?

EDIT2: OK, vì vậy hai giải pháp của tôi doen't công việc - đây là lý do tại sao: Các ContentPresenter được nội dung của nó từ DataContext, nhưng tôi cần các ràng buộc trên chứa các yếu tố liên kết đến các cửa sổ ban đầu (mẹ UserControl của trong cây logic) DataContext - bởi vì cách này, khi tôi nhúng hộp văn bản bị ràng buộc vào tài sản của ViewModel, nó không bị ràng buộc, như là chuỗi thừa kế đã bị phá vỡ bên trong điều khiển!

Có vẻ như tôi sẽ cần lưu DataContext của cha mẹ và khôi phục nó cho con của tất cả các vùng chứa của điều khiển, nhưng tôi không nhận được bất kỳ sự kiện nào mà DataContext trong cây logic đã thay đổi.

EDIT3: Tôi có giải pháp!, đã xóa các aswers trước đó của tôi. Xem câu trả lời của tôi.

Trả lời

29

OK, giải pháp của tôi là hoàn toàn không cần thiết, đây là những hướng dẫn duy nhất, bạn sẽ cần cho việc tạo ra bất kỳ điều khiển người dùng:

Tóm tắt:

Phân lớp một số lớp phù hợp (hoặc UIElement nếu không phù hợp với bạn) - tệp chỉ đơn giản là * .cs, vì chúng tôi chỉ xác định hành vi, chứ không phải giao diện của điều khiển.

public class EnhancedItemsControl : ItemsControl 

Thêm thuộc tính phụ thuộc cho 'vị trí' của bạn (tài sản bình thường không đủ tốt vì nó chỉ hỗ trợ giới hạn ràng buộc).Mát lừa: trong VS, viết propdp và nhấn tab để mở rộng đoạn :):

public object AlternativeContent 
{ 
    get { return (object)GetValue(AlternativeContentProperty); } 
    set { SetValue(AlternativeContentProperty, value); } 
} 

// Using a DependencyProperty as the backing store for AlternativeContent. This enables animation, styling, binding, etc... 
public static readonly DependencyProperty AlternativeContentProperty = 
    DependencyProperty.Register("AlternativeContent" /*name of property*/, typeof(object) /*type of property*/, typeof(EnhancedItemsControl) /*type of 'owner' - our control's class*/, new UIPropertyMetadata(null) /*default value for property*/); 

Thêm một thuộc tính cho một nhà thiết kế (vì bạn đang tạo ra cái gọi là kiểm soát lookless), cách này chúng ta nói rằng chúng ta cần để có một ContentPresenter gọi PART_AlternativeContentPresenter trong mẫu của chúng tôi

[TemplatePart(Name = "PART_AlternativeContentPresenter", Type = typeof(ContentPresenter))] 
public class EnhancedItemsControl : ItemsControl 

Cung cấp một constructor tĩnh sẽ cho vào hệ thống phong cách WPF về lớp của chúng tôi (không có nó, phong cách/mẫu mà nhắm mục tiêu loại mới của chúng tôi sẽ không được áp dụng):

static EnhancedItemsControl() 
{ 
    DefaultStyleKeyProperty.OverrideMetadata(
     typeof(EnhancedItemsControl), 
     new FrameworkPropertyMetadata(typeof(EnhancedItemsControl))); 
} 

Nếu bạn muốn làm điều gì đó với ContentPresenter từ mẫu, bạn làm điều đó bằng cách ghi đè các phương pháp OnApplyTemplate:

//remember that this may be called multiple times if user switches themes/templates! 
public override void OnApplyTemplate() 
{ 
    base.OnApplyTemplate(); //always do this 

    //Obtain the content presenter: 
    contentPresenter = base.GetTemplateChild("PART_AlternativeContentPresenter") as ContentPresenter; 
    if (contentPresenter != null) 
    { 
     // now we know that we are lucky - designer didn't forget to put a ContentPresenter called PART_AlternativeContentPresenter into the template 
     // do stuff here... 
    } 
} 

Cung cấp một mẫu mặc định: luôn trong ProjectFolder/Themes/Generic.xaml (Tôi có dự án độc lập của tôi với tất cả các điều khiển wpf có thể sử dụng phổ biến, sau đó được tham chiếu từ các giải pháp khác). Đây chỉ là nơi mà hệ thống sẽ tìm mẫu cho các điều khiển của bạn, vì vậy hãy đặt mẫu mặc định cho tất cả các điều khiển trong dự án tại đây: Trong đoạn mã này, tôi đã xác định một ContentPresenter mới hiển thị giá trị thuộc tính đính kèm AlternativeContent của chúng tôi. Lưu ý cú pháp - Tôi có thể sử dụng hoặc Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" hoặc Content="{TemplateBinding AlternativeContent}", nhưng trước đây sẽ hoạt động nếu bạn xác định mẫu bên trong mẫu của bạn (cần thiết để tạo kiểu cho ví dụ ItemPresenters).

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:WPFControls="clr-namespace:MyApp.WPFControls" 
    > 

    <!--EnhancedItemsControl--> 
    <Style TargetType="{x:Type WPFControls:EnhancedItemsControl}"> 
     <Setter Property="Template"> 
      <Setter.Value> 
       <ControlTemplate TargetType="{x:Type WPFControls:EnhancedItemsControl}"> 
        <ContentPresenter 
         Name="PART_AlternativeContentPresenter" 
         Content="{Binding AlternativeContent, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" 
         DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type WPFControls:EnhancedItemsControl}}}" 
         /> 
       </ControlTemplate> 
      </Setter.Value> 
     </Setter> 
    </Style> 

</ResourceDictionary> 

Thì đấy, bạn vừa tạo UserControl không cần nhìn đầu tiên của bạn (thêm nội dung hơn và thuộc tính phụ thuộc để có thêm 'vùng nội dung').

+4

Cảm ơn chúa đã giải thích điều này một cách đơn giản và dễ hiểu! Thật khó để tìm tài nguyên về điều này, hoặc nó chỉ là một lỗi wpf-beginners: p – Shion

2

Nếu bạn đang sử dụng một usercontrol

Tôi đoán bạn thực sự muốn:

<ContentPresenter Content="{Binding Buttons}"/> 

này giả định rằng DataContext truyền cho kiểm soát của bạn có một tài sản Buttons.

Và với một ControlTemplate

Các tùy chọn khác sẽ là một ControlTemplate và sau đó bạn có thể sử dụng:

<ContentPresenter ContentSource="Header"/> 

Bạn sẽ cần phải được templating một điều khiển mà thực sự có một 'Tiêu đề' để làm điều này (thường là một HeaderedContentControl).

+0

Cảm ơn - Tôi không muốn sử dụng ràng buộc, vì tôi muốn gán một DataContext (ViewModel) cho toàn bộ cửa sổ (bằng với View), và sau đó liên kết với các lệnh từ các nút được chèn vào 'khe' điều khiển - bất kỳ việc sử dụng ràng buộc nào trong cấu trúc phân cấp sẽ phá vỡ sự kế thừa của DC của View. Đối với tùy chọn khác - có, trong trường hợp này bắt nguồn từ HeaderedContentControl sẽ hoạt động, nhưng nếu tôi muốn ba phần thì sao? Làm thế nào để làm cho "HeaderedAndFooteredContentControl" của riêng tôi (hoặc, làm thế nào tôi sẽ thực hiện HeaderedContentControl nếu tôi không có một)? –

4

Hasta la victoria siempre!

Tôi đã đi kèm với giải pháp (lần đầu tiên trên mạng Internet, có vẻ như với tôi :))

Các DialogControl.xaml.cs khéo léo làm việc - xem nhận xét:

public partial class DialogControl : UserControl 
{ 
    public DialogControl() 
    { 
     InitializeComponent(); 

     //The Logical tree detour: 
     // - we want grandchildren to inherit DC from this (grandchildren.DC = this.DC), 
     // but the children should have different DC (children.DC = this), 
     // so that children can bind on this.Properties, but grandchildren bind on this.DataContext 
     this.InnerWrapper.DataContext = this; 
     this.DataContextChanged += DialogControl_DataContextChanged; 
     // need to reinitialize, because otherwise we will get static collection with all buttons from all calls 
     this.Buttons = new ObservableCollection<FrameworkElement>(); 
    } 


    void DialogControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     /* //Heading is ours, we want it to inherit this, so no detour 
     if ((this.GetValue(HeadingProperty)) != null) 
      this.HeadingContainer.DataContext = e.NewValue; 
     */ 

     //pass it on to children of containers: detours 
     if ((this.GetValue(ControlProperty)) != null) 
      ((FrameworkElement)this.GetValue(ControlProperty)).DataContext = e.NewValue; 

     if ((this.GetValue(ButtonProperty)) != null) 
     { 
      foreach (var control in ((ObservableCollection<FrameworkElement>) this.GetValue(ButtonProperty))) 
      { 
       control.DataContext = e.NewValue; 
      } 
     } 
    } 

    public FrameworkElement Control 
    { 
     get { return (FrameworkElement)this.GetValue(ControlProperty); } 
     set { this.SetValue(ControlProperty, value); } 
    } 

    public ObservableCollection<FrameworkElement> Buttons 
    { 
     get { return (ObservableCollection<FrameworkElement>)this.GetValue(ButtonProperty); } 
     set { this.SetValue(ButtonProperty, value); } 
    } 

    public string Heading 
    { 
     get { return (string)this.GetValue(HeadingProperty); } 
     set { this.SetValue(HeadingProperty, value); } 
    } 

    public static readonly DependencyProperty ControlProperty = 
      DependencyProperty.Register("Control", typeof(FrameworkElement), typeof(DialogControl)); 
    public static readonly DependencyProperty ButtonProperty = 
      DependencyProperty.Register(
       "Buttons", 
       typeof(ObservableCollection<FrameworkElement>), 
       typeof(DialogControl), 
       //we need to initialize this for the designer to work correctly! 
       new PropertyMetadata(new ObservableCollection<FrameworkElement>())); 
    public static readonly DependencyProperty HeadingProperty = 
      DependencyProperty.Register("Heading", typeof(string), typeof(DialogControl)); 
} 

Và DialogControl. XAML (không thay đổi): sử dụng

<UserControl x:Class="TkMVVMContainersSample.Views.Common.DialogControl" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" 
    > 
    <DockPanel x:Name="InnerWrapper"> 
     <DockPanel 
      LastChildFill="False" 
      HorizontalAlignment="Stretch" 
      DockPanel.Dock="Bottom"> 
      <ItemsControl 
       x:Name="ButtonsContainer" 
       ItemsSource="{Binding Buttons}" 
       DockPanel.Dock="Right" 
       > 
       <ItemsControl.ItemTemplate> 
        <DataTemplate> 
         <Border Padding="8"> 
          <ContentPresenter Content="{TemplateBinding Content}" /> 
         </Border> 
        </DataTemplate> 
       </ItemsControl.ItemTemplate> 
       <ItemsControl.ItemsPanel> 
        <ItemsPanelTemplate> 
         <StackPanel Orientation="Horizontal" Margin="8"> 
         </StackPanel> 
        </ItemsPanelTemplate> 
       </ItemsControl.ItemsPanel> 
      </ItemsControl> 
     </DockPanel> 
     <Border 
      Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" 
      Padding="8,0,8,8" 
      > 
      <StackPanel> 
       <Label 
        x:Name="HeadingContainer" 
        Content="{Binding Heading}" 
        FontSize="20" 
        Margin="0,0,0,8" /> 
       <ContentPresenter 
        x:Name="ControlContainer" 
        Content="{Binding Control}"     
        /> 
      </StackPanel> 
     </Border> 
    </DockPanel> 
</UserControl> 

mẫu:

<Window x:Class="TkMVVMContainersSample.Services.TaskEditDialog.ItemEditView" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Common="clr-namespace:TkMVVMContainersSample.Views.Common" 
    Title="ItemEditView" 
    > 
    <Common:DialogControl> 
     <Common:DialogControl.Heading> 
      Edit item 
     </Common:DialogControl.Heading> 
     <Common:DialogControl.Control> 
      <!-- Concrete dialog's content goes here --> 
      <Grid> 
       <Grid.RowDefinitions> 
        <RowDefinition Height="Auto" /> 
        <RowDefinition Height="Auto" /> 
       </Grid.RowDefinitions> 
       <Grid.ColumnDefinitions> 
        <ColumnDefinition Width="Auto" /> 
        <ColumnDefinition Width="*" /> 
       </Grid.ColumnDefinitions> 

       <Label Grid.Row="0" Grid.Column="0">Name</Label> 
       <TextBox Grid.Row="0" Grid.Column="1" MinWidth="160" TabIndex="1" Text="{Binding Name}"></TextBox> 
       <Label Grid.Row="1" Grid.Column="0">Phone</Label> 
       <TextBox Grid.Row="1" Grid.Column="1" MinWidth="160" TabIndex="2" Text="{Binding Phone}"></TextBox> 
      </Grid> 
     </Common:DialogControl.Control> 
     <Common:DialogControl.Buttons> 
      <!-- Concrete dialog's buttons go here --> 
      <Button Width="80" TabIndex="100" IsDefault="True" Command="{Binding OKCommand}">OK</Button> 
      <Button Width="80" TabIndex="101" IsCancel="True" Command="{Binding CancelCommand}">Cancel</Button> 
     </Common:DialogControl.Buttons> 
    </Common:DialogControl> 

</Window> 
+4

Vui lòng không làm điều này, sử dụng câu trả lời thứ hai của tôi http://stackoverflow.com/questions/1029955/wpf-template-or-usercontrol-with-2-or-more-contentpresenters-to-present-conte/1658916# 1658916 thay thế! –

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