WPF: шаблон или UserControl с 2 (или больше!) ContentPresenters для представления содержания в 'слотах'

Я столкнулся с этой проблемой и получал все те же сообщения об ошибках при создании самого первого приложения в Visual Studio Professional 2015 (с Xamarin). Не знаю, пригодится ли это кому-нибудь еще, но мы наткнулись на то, что решило проблему для нас.

В MainActivity.cs есть некоторый код по умолчанию, когда вы впервые открываете проект «Пустое приложение», но мы удалили часть этого кода и скопировали / вставили поверх него. Вот как это выглядит изначально:

    namespace App3
{
    [Activity(Label = "App3", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.MyButton);

            button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
        }
    }
}

Чтобы исправить это: мы попытались вернуть эти строки в код MainActivity.cs:

[Activity(Label = "App3", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            // Get our button from the layout resource,
            // and attach an event to it
            Button button = FindViewById<Button>(Resource.Id.MyButton);

И затем мы запустили код и ошибка ушла. Возможно, просто глупая ошибка, и она не всегда решит проблему, но она сработала для нас.

25
задан Community 23 May 2017 в 12:08
поделиться

3 ответа

Хорошо, мое решение было совершенно ненужным, вот единственные руководства, которые вам когда-либо понадобятся для создания любого пользовательского элемента управления:

Вкратце:

Подкласс некоторого подходящего класса (или UIElement если вам ничего не подходит) - это просто файл * .cs, поскольку мы определяем только поведение, а не внешний вид элемента управления.

public class EnhancedItemsControl : ItemsControl

Добавьте свойство зависимости для ваших «слотов» (обычное свойство недостаточно хорошо, поскольку оно имеет только ограниченную поддержку привязки). Классный трюк: в VS напишите propdp и нажмите вкладку, чтобы развернуть фрагмент :):

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*/);

Добавьте атрибут для дизайнера (поскольку вы создаете так называемый элемент управления без внешнего вида), таким образом мы говорим, что нам нужно иметь ContentPresenter с именем PART_AlternativeContentPresenter в нашем шаблоне.

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

Предоставьте статический конструктор, который сообщит стилю WPF system о нашем классе (без него стили / шаблоны, нацеленные на наш новый тип, не будут применяться):

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

Если вы хотите что-то сделать с ContentPresenter из шаблона, вы делаете это, переопределив метод 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...
    }
}

Предоставьте шаблон по умолчанию: всегда в ProjectFolder / Themes / Generic.xaml (у меня есть автономный проект со всеми настраиваемыми универсально используемыми элементами управления wpf, на которые затем ссылаются другие решения). Это единственное место, где система будет искать шаблоны для ваших элементов управления, поэтому поместите здесь шаблоны по умолчанию для всех элементов управления в проекте: В этом фрагменте я определил новый ContentPresenter, который отображает значение нашего присоединенного свойства AlternativeContent . Обратите внимание на синтаксис - я мог бы использовать либо Content = "{Binding AlternativeContent, RelativeSource = {RelativeSource FindAncestor, AncestorType = {x: Type WPFControls: EnhancedItemsControl}}}" или Content = "{TemplateBinding AlternativeContent}" , но первый будет работать, если вы определите шаблон внутри своего шаблона (необходимый для стилизации, например, 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>

Вуаля, вы только что создали свой первый незаметный UserControl (добавьте больше contentpresenters и свойств зависимостей для дополнительных «слотов контента»).

31
ответ дан 28 November 2019 в 21:25
поделиться

Если вы используете UserControl

, я предполагаю, что вы действительно хотите:

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

Это предполагает, что DataContext, переданный вашему элементу управления, имеет свойство Buttons.

И с a ControlTemplate

Другой вариант - ControlTemplate, а затем вы можете использовать:

<ContentPresenter ContentSource="Header"/>

Для этого вам потребуется создать шаблон элемента управления, который на самом деле имеет заголовок (обычно HeaderedContentControl).

2
ответ дан 28 November 2019 в 21:25
поделиться

Hasta la victoria siempre!

Я пришел с рабочим решением (сначала в Интернете, как мне кажется :))

Хитрый DialogControl.xaml.cs - см. Комментарии :

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

И DialogControl.xaml (без изменений):

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

Пример использования:

<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
ответ дан 28 November 2019 в 21:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: