Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

WPF: Стили, изменение содержимого переопределенного Content

9.7K
19 июня 2011 года
Vitamant
228 / / 07.02.2011
Доброго времени суток! Делаю первые шаги в WPF и сразу столкнулся со следующей проблемой:

Написал стиль для кнопок моего приложения, который вместо обычного текста выводит вначале иконку, а за ним уже текстовый блок. Вот он:
 
Код:
<Style TargetType="Button">
    <Setter Property="Content">
        <Setter.Value>
            <StackPanel Orientation="Horizontal">
                <Image Width="16" Height="16" Stretch="Fill" Margin="3,0,3,0" Source="{Binding}"/>
                <TextBlock Text="{Binding}" Margin="3,0,3,0"/>
            </StackPanel>
        </Setter.Value>
    </Setter>
</Style>

А теперь, собственно, вопрос - как создать кнопку, использующую этот стиль, и задать ей значения Text и Image Source? {Binding} тут вставлен на обум - подсмотрен в чьем-то примере. Или стили для этого не подходят и мне нужно создавать собственный класс кнопки, наследуясь от Button, добавлять в него новые свойства и встроенный стиль?

Помогите, гуру!
Заранее спасибо.
9.7K
19 июня 2011 года
Vitamant
228 / / 07.02.2011
Так, уже осознал, что для этих целей используются не стили, а шаблоны (Templates). И для связи со свойствами контрола используется {TemplateBinding}. Все бы ничего, но он требует реально существующее свойство контрола. Получается, если я хочу создать "закрытую" кнопку без ContentPresenter'а, но отображающую текст и иконку, я должен либо написать конвертер, который будет парсить переданный Content (и как следствие создавать дополнительную нагрузку), либо все же создать свой собственный контрол, отличающийся от стандартной кнопки только наличем пары дополнителньых свойств? А Роман (Здебский) говорил, что это требуется только для смены поведенческой модели... =\

---

Нарисовал такое вот чудовище:
 
Код:
<ControlTemplate x:Key="MyTemplate" TargetType="Button">
    <Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
            <ContentPresenter Width="16" Height="16" Content="{TemplateBinding Tag}"/>
            <TextBlock Text="{TemplateBinding Content}" Margin="3,0,3,0"/>
        </StackPanel>
    </Grid>
</ControlTemplate>

ContentPresenter, который нужен только для вывода передаваемого Image'а, биндинг по полю Tag... В общем, все страшно. Вопрос - как это облагородить? Как делать правильно и оптимально?
5
19 июня 2011 года
hardcase
4.5K / / 09.08.2005
Цитата: Vitamant
либо все же создать свой собственный контрол, отличающийся от стандартной кнопки только наличем пары дополнителньых свойств? А Роман (Здебский) говорил, что это требуется только для смены поведенческой модели... =\


Никто не мешает наследоваться от класса Button, либо от ButtonBase, добавив необходимые зависимые свойства.
Если изображение зависит от контекста данных (DataContext) то, конечно, наследование не нужно.

Цитата: Vitamant

ContentPresenter, который нужен только для вывода передаваемого Image'а, биндинг по полю Tag... В общем, все страшно. Вопрос - как это облагородить? Как делать правильно и оптимально?


Я бы создал свой класс кнопки (на основе ButtonBase), прикрутив к нему стиль, основанный на стандартном.

9.7K
19 июня 2011 года
Vitamant
228 / / 07.02.2011
Понятно. Так и поступлю.

---

Хм, возникла проблема. Тут. конечно, наверняка мои руки, так как я еще слабо ориентируюсь в системе нэймспейсов WPF, но тем не менее...
Создал в Solution Explorer папочку Controls. Добавил новый класс - ImageButton. Унаследовал его от Button. В его XAML изменил теги <UserControl> на <Button>. Внешний вид пустого прямоугольника изменился.
Код:
<Button x:Class="XNARPGEditor.Controls.ImageButton"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
           
    </Grid>
</Button>


В ресурсном словаре, содержащем стили кнопок создаю DataTemplate для своего контрола, подключив соответствующее пространство имен:
 
Код:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:c="clr-namespace:XNARPGEditor.Controls">
...
<ControlTemplate x:Key="iconButtonTemplate" TargetType="{x:Type c:ImageButton}">


Компиляция прохоидт на ура, но при запуске получаю следующую ошибку:
"Не удалось создать "Type" на основе текста "c:ImageButton".": номер строки "116" и позиция в строке "49".

Заменяю на:
 
Код:
<ControlTemplate x:Key="iconButtonTemplate" TargetType="XNARPGEditor.Controls.ImageButton">

Получаю наналогичную ошибку:
"Не удалось создать "TargetType" на основе текста "XNARPGEditor.Controls.ImageButton".": номер строки "116" и позиция в строке "49".

В чем проблема? Везде рассматриваются какие-то элементарные ситуации, когда вся эта пакость лежит в одном пространстве имен. >_>
5
19 июня 2011 года
hardcase
4.5K / / 09.08.2005
Цитата: Vitamant

Создал в Solution Explorer папочку Controls. Добавил новый класс - ImageButton. Унаследовал его от Button. В его XAML изменил теги <UserControl> на <Button>.


Вот это было зря. Нужно следующим образом:
1) Создаем класс, наслеованный от Button:

 
Код:
namespace MyControls
{
  public class ImageButton : Button { }
}

2) Создаем ResourceDictionary-файл Themes\Generic.xaml, ставим ему тип Embedded Resource
В этом файле создаем стиль:
 
Код:
<ResourceDictionary xmlns:local="clr-namespace:MyControls">
  <Style TargetType="local:ImageButton">
    <!-- тут и будет стиль кнопки -->
  </Style>
..


В принципе, можно подключить этот стиль обычным образом - через App.xaml.

Писал в браузере, надеюсь поможет.
9.7K
20 июня 2011 года
Vitamant
228 / / 07.02.2011
Увы, ничего не изменилось:
Цитата:
"Не удалось создать "Type" на основе текста "local:ImageButton".": номер строки "116" и позиция в строке "49".

5
20 июня 2011 года
hardcase
4.5K / / 09.08.2005
Цитата: Vitamant
Увы, ничего не изменилось:


Ок. Вот шаги создания собственного контрола:
1) Создаем класс MyCustomButton

 
Код:
using System.Windows.Controls.Primitives;

namespace MyWpfApplication.Controls
{
    public class MyCustomButton : ButtonBase
    {
    }
}

2) Создаем стиль Style\MyCustomControl.xaml, ставим ему Build Action = Resource:
Код:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyWpfApplication.Controls;assembly=MyWpfApplication"> <!-- ВАЖНО: имя сборки указывать нужно! -->
    <Style TargetType="{x:Type local:MyCustomButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyCustomButton}">
                    <Border Background="Red" BorderBrush="Bisque" BorderThickness="2">
                        <ContentPresenter Content="{TemplateBinding Content}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

3) В App.xaml дописываем ссылку на ресурс:
Код:
<Application x:Class="MyWpfAppliction.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Styles\MyCustomButtonStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

4) Используем
 
Код:
<Window x:Class="MyWpfApplication.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyWpfApplication.Controls"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <local:MyCustomButton Content="Text" />
    </Grid>
</Window>




P.S. Я с WPF дела не имею, занимаясь лишь Silverlight-ом.
9.7K
20 июня 2011 года
Vitamant
228 / / 07.02.2011
Ура! Спасибо огромное! Работает хреновина! ^_^

---

О! Получен ответ. Я попутно связался с Романом Здебским и задал тот же вопрос. Он предложил то же решение, но заодно познакомил с ведущим экспертом MVP по WPF - Александром Шером. И на наши размышлизмы он ответил так:
Цитата:
Наследоваться ни от чего не надо. Надо создать и использовать AttachedProperty. В приведенном Вами примере надо создать AttachedProperty, которое будет передавать путь или ресурс иконки.



Сейчас попробую реализовать. Но в любом случае, hardcase, тебе еще раз огромное спасибо - на будущее буду знать, как делать собственные контролы! :)

---

Для Silverilight тут:
http://silverlight.su/viewtopic.php?id=223
Надеюсь, в WPF аналогичным образом...

5
20 июня 2011 года
hardcase
4.5K / / 09.08.2005
Цитата: Vitamant
WPF - Александром Шером. И на наши размышлизмы он ответил так


Да, про аттачнутые свойства я-то и забыл. Они в исходной задаче в самый раз подходят. :)

9.7K
20 июня 2011 года
Vitamant
228 / / 07.02.2011
Аха, только вот что-то никак мне не получается задружиться с WPF. Чем был хорош WinForms - посылали в совершенно определенное место, а не:
Цитата:
"XNARPGEditor.AttachedProperties.Button" выдал исключение.



---

Аха! Нашел более полную информацию:

Цитата:
Тип значения по умолчанию не совпадает с типом свойства "Icon".
в System.Windows.DependencyProperty.ValidateDefaultValueCommon(Object defaultValue, Type propertyType, String propertyName, ValidateValueCallback validateValueCallback, Boolean checkThreadAffinity)
в System.Windows.DependencyProperty.RegisterCommon(String name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
в System.Windows.DependencyProperty.RegisterAttached(String name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
в XNARPGEditor.AttachedProperties.Button..cctor() в D:\Coding\C#\XNA\XNARPGEditor\Attached Properties\Button.cs:строка 12



---

Даааааааааааааа!!! Работает хреновина! ^_^ Ура! Щаз такого наворочу!..

---

Потомкам:

Код:
namespace XNARPGEditor.AttachedProperties
{
    public static class Button
    {
        public static readonly DependencyProperty IconProperty =
            DependencyProperty.RegisterAttached(
                "Icon",
                typeof(Image),
                typeof(FrameworkElement),
                new PropertyMetadata(null)); // вот здесь я забывал выставить значение по-умолчанию

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static Image GetIcon(FrameworkElement ctl)
        {
            return (Image)ctl.GetValue(IconProperty);
        }

        public static void SetIcon(FrameworkElement ctl, Image value)
        {
            ctl.SetValue(IconProperty, value);
        }
    }
}
5
20 июня 2011 года
hardcase
4.5K / / 09.08.2005
Оффтоп :)
Цитата: Vitamant
Чем был хорош WinForms - посылали в совершенно определенное место, а не:


В WPF вполне осмысленные сообщения, а вот в Silverlight полный мрак с этим - приходится зачастую полагаться на телепатию.

9.7K
21 июня 2011 года
Vitamant
228 / / 07.02.2011
Оффтоп :)
Заинтриговал! Надо будет обязательно попробовать! :)
9.7K
23 июня 2011 года
Vitamant
228 / / 07.02.2011
Казалось бы, проблема решена... ан нет. Все было здорово, пока на кнопочки я любовался в редакторе. При попытке же запустить откомпилированный проект, я получаю исключение о том, что DependencyProperty не получилось создать на основе текста ap:Button.Icon. (При этом в редакторе, все отображается и нормально работает).

Вот немного видоизменившийся класс, описывающий свойство расширения:
Код:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

namespace AttachedProperties
{
    public class Button
    {
        public static readonly DependencyProperty IconProperty =
            DependencyProperty.RegisterAttached(
                "Icon",
                typeof(BitmapImage),
                typeof(FrameworkElement),
                new PropertyMetadata(null));

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static BitmapImage GetIcon(FrameworkElement ctl)
        {
            return (BitmapImage)ctl.GetValue(IconProperty);
        }

        public static void SetIcon(FrameworkElement ctl, BitmapImage value)
        {
            ctl.SetValue(IconProperty, value);
        }
    }
}


Окошко, в котором разворачивается действо:
Код:
<Window x:Class="XNARPGEditor.Characteristics"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:res="clr-namespace:XNARPGEditor.Properties"
        xmlns:local="clr-namespace:XNARPGEditor"
        xmlns:ap="clr-namespace:AttachedProperties;assembly=XNARPGEditor"
        Title="Characteristics" Height="304" Width="800" Loaded="Window_Loaded">
    <Grid>
                        <Button Style="{StaticResource ResourceKey=IconButtonStyle}" ap:Button.Icon="{StaticResource ResourceKey=iconOkGreen}" Height="23" Margin="5" Name="acceptButton" Grid.Column="0" Click="acceptButton_Click" Content="{x:Static res:Resources.uAccept}"/>
    </Grid>
</Window>

Картинка описывается в App.xml:
 
Код:
<BitmapImage x:Key="iconOkGreen" x:Shared="false" DecodePixelHeight="16" DecodePixelWidth="16" UriSource="Images/Interface/OkGreen.ico"/>

И, как я уже говорил, и в студии, и в блендере все корректно отображается, компилируется без проблем. Но при запуске - ошибка.
Что интересно:
если я в Window (в стилях работает нормально), укажу сборку, то, при попытке комплияции, получу ошибку:
Цитата:
Ошибка 1 свойство "Button.Icon" не существует в пространстве имен XML "clr-namespace:AttachedProperties;assembly=XNARPGEditor". Строка 95 позиция 86. D:\Coding\C#\XNA\XNARPGEditor\Windows\Characteristics.xaml 95 86 XNARPGEditor


Я так понимаю, проблема именно в этом... но никак не могу разобраться - почему...

Собственно, само исключение:

Цитата:
System.Windows.Markup.XamlParseException не обработано
Message=Не удалось создать "DependencyProperty" на основе текста "ap:Button.Icon".
Source=PresentationFramework
LineNumber=0
LinePosition=0
StackTrace:
в System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
в System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)
в System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)
в System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)
в System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)
в System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)
в System.Windows.FrameworkElement.ApplyTemplate()
в System.Windows.FrameworkElement.MeasureCore(Size availableSize)

...

в MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
в MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
в System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
в System.Windows.Application.RunInternal(Window window)
в System.Windows.Application.Run()
в XNARPGEditor.App.Main() в D:\Coding\C#\XNA\XNARPGEditor\obj\x86\Debug\App.g.cs:строка 0
в System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
в Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
в System.Threading.ThreadHelper.ThreadStart()
InnerException: System.NotSupportedException
Message=DependencyPropertyConverter не может выполнить преобразование из System.String.
Source=System
StackTrace:
в System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
в System.Windows.Markup.DependencyPropertyConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object source)
в MS.Internal.Xaml.Runtime.ClrObjectRuntime.CreateObjectWithTypeConverter(ServiceProviderContext serviceContext, XamlValueConverter`1 ts, Object value)
в System.Xaml.XamlObjectWriter.Logic_CreateFromValue(ObjectWriterContext ctx, XamlValueConverter`1 typeConverter, Object value, XamlMember property, String targetName, IAddLineInfo lineInfo)
InnerException:



...только сейчас заметил затерявшуюся в дебрях строчку:

Цитата:
Message=DependencyPropertyConverter не может выполнить преобразование из System.String.


Но я не понимаю - к чему это! Где он нашел эти несчастные строки?!

---

Ах да, если в стилях закомментировать вывод изображения - все работает на ура! Вот тело шаблона:

 
Код:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
     <Image Source="{TemplateBinding ap:Button.Icon}"/>
     <ContentPresenter Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" />
</StackPanel>
9.7K
25 июня 2011 года
Vitamant
228 / / 07.02.2011
Ура! Проблема решена методом научного тыка!
Код:
public static class Buttons
    {
        public static readonly DependencyProperty IconProperty =
            DependencyProperty.RegisterAttached(
                "Icon",
                typeof(BitmapImage),
                typeof(Buttons), // вот здесь была ошибка
                new PropertyMetadata(null));

        [AttachedPropertyBrowsableForType(typeof(Control))]
        public static BitmapImage GetIcon(UIElement ctl)
        {
            return (BitmapImage)ctl.GetValue(IconProperty);
        }

        public static void SetIcon(UIElement ctl, BitmapImage value)
        {
            ctl.SetValue(IconProperty, value);
        }
    }

Осознание выдержки из MSDN:
Цитата:
ownerType
Тип: System.Type
Тип владельца, регистрирующего свойство зависимостей.


Пришло, как обычно, уже после. Нужно указывать класс, регистриующий свойство, а я указывал класс кнопки.

74K
16 сентября 2011 года
Serdceder
6 / / 16.09.2011
Не исключено, что я просто не разобрался в ваших хитросплетениях со стилями, но, закрывая глаза на мнение MVP WPF специалиста, для чего столько работы?
Button сам по себе поддерживает вложенность элементов. Почему просто не написать вот так:
<Button Click="btnAdd" Height="20" >
<Grid>
<Image Source="./Pic/Fon.jpg" Height="20" Stretch="Fill" />
<TextBlock Text="_Add" Height="16"/>
</Grid>
</Button>

Если вам нужно изменять картинки, сделайте просто Binding на сво-во Source, создайте сво-во источник (только не забудьте унаследовать от INotifPropertyChange, либо просто перенесите ранее созданное DepenecyProperty, из-за которого возникло столько работы, иначе оно не будет отслеживать изменения), и через FindAncesstor либо через ElementName (тогда нужно в XAML-разметке объявить имя окна через x:Name) привязать это сво-во.
Можно сделать ещё проще, в DepencyProperty создать обработчик при изменении, и в нём ручками привязать e.NewValue к Image.Source, только здесь нужно будет кнопку разобрать на составляющие, это если не ошибаюсь позволяет сделать FindTemplate.
9.7K
15 марта 2012 года
Vitamant
228 / / 07.02.2011
1) Стараюсь следовать декларативному подходу и поменьше прибегать к коду.
2) А если таких кнопок 20? Представляешь - на что будет похоже XML-описание окна? А ведь в нем не только кнопки!
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог