Как зайти в Даркнет?!
25th January, 01:11
6
0
Как в tkinter из поля ввода Entry получить значение в одну переменную и обновить строку кнопкой, затем получить ещё одно введённое значение и затем сложить их. Ниже пример кода
21st July, 19:00
894
0
Программа, которая создает фейковые сервера в поиске игровых серверов CS 1.6 Steam
21st March, 17:43
948
0
Очень долго работает Update запрос Oracle
27th January, 09:58
914
0
не могу запустить сервер на tomcat HTTP Status 404 – Not Found
21st January, 18:02
905
0
Где можно найти фрилансера для выполнения поступающих задач, на постоянной основе?
2nd December, 09:48
938
0
Разработка мобильной кроссплатформенной военной игры
16th July, 17:57
1724
0
период по дням
25th October, 10:44
3955
0
Пишу скрипты для BAS только на запросах
16th September, 02:42
3720
0
Некорректный скрипт для закрытия блока
14th April, 18:33
4613
0
прокидывать exception в блоках try-catch JAVA
11th March, 21:11
4381
0
Помогите пожалуйста решить задачи
24th November, 23:53
6086
0
Не понимаю почему не открывается детальное описание продукта
11th November, 11:51
4350
0
Нужно решить задачу по программированию на массивы
27th October, 18:01
4396
0
Метода Крамера С++
23rd October, 11:55
4309
0
помогите решить задачу на C++
22nd October, 17:31
4002
0
Помогите решить задачу на python с codeforces
22nd October, 11:11
4492
0
Python с нуля: полное руководство для начинающих
18th June, 13:58
2599
0
Как применить несколько стилей в WPF
В WPF, как бы я применил несколько стилей к FrameworkElement ? Например, у меня есть элемент управления, который уже имеет стиль. У меня также есть отдельный стиль, который я хотел бы добавить к нему, не сдувая первый. Стили имеют разные TargetTypes, поэтому я не могу просто расширить один с другим.
Я думаю, что простой ответ заключается в том, что вы не можете сделать (по крайней мере, в этой версии WPF) то, что вы пытаетесь сделать.
То есть для любого конкретного элемента может быть применен только один стиль.
Однако, как уже говорилось выше, возможно, вы можете использовать BasedOn , чтобы помочь вам. Проверьте следующий фрагмент свободной xaml. В нем вы увидите, что у меня есть базовый стиль, который задает свойство, существующее в базовом классе элемента, к которому я хочу применить два стиля. И во втором стиле, который основан на базовом стиле, я задаю другое свойство.
Итак, идея здесь ... это если вы можете каким-то образом отделить свойства, которые вы хотите установить ... в соответствии с иерархией наследования элемента, на который вы хотите установить несколько стилей ... возможно, у вас есть обходной путь.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50"/>
</Grid>
</Page>
Надеюсь, это поможет.
Примечание:
Следует особо отметить одну вещь. Если вы измените TargetType во втором стиле (в первом наборе xaml выше) на ButtonBase , эти два стиля не будут применены. Однако, чтобы обойти это ограничение, ознакомьтесь со следующим xaml ниже. В принципе, это означает, что вам нужно дать стилю ключ и ссылаться на него с помощью этого ключа.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
</Grid>
</Page>
У Беа Штольниц был хороший пост в блоге об использовании расширения markup для этого, под заголовком "How can I set multiple styles in WPF?"
Этот блог теперь мертв, поэтому я воспроизводю сообщение здесь
WPF и Silverlight предлагают возможность получения стиля из другого стиля через свойство “BasedOn”. Эта функция позволяет разработчикам организовать свои стили с помощью иерархии, аналогичной наследованию классов. Рассмотрим следующие стили:
<Style TargetType="Button" x:Key="BaseButtonStyle">
<Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
<Setter Property="Foreground" Value="Red" />
</Style>
С помощью этого синтаксиса кнопка, использующая RedButtonStyle, будет иметь свойством переднего плана красный цвет, а свойством поля-10.
Эта функция уже давно существует в WPF, и она является новой в Silverlight 3.
Что делать, если вы хотите установить более одного стиля на элемент? Ни WPF, ни Silverlight не дают решения этой проблемы из коробки. К счастью, есть способы реализовать это поведение в WPF, которые я буду обсуждать в этом блоге.
WPF и Silverlight используют расширения markup для предоставления свойств со значениями, для получения которых требуется некоторая логика. Markup расширения легко узнаваемы по наличию фигурных скобок, окружающих их в XAML. Например, расширение {Binding} markup содержит логику для извлечения значения из источника данных и обновления его при возникновении изменений; расширение {StaticResource} markup содержит логику для извлечения значения из словаря ресурсов на основе ключа. К счастью для нас, WPF позволяет пользователям писать свои собственные пользовательские расширения markup. Эта функция еще не присутствует в Silverlight, поэтому решение в этом блоге применимо только к WPF.
Другие написали замечательные решения для слияния двух стилей с помощью расширений markup. Однако я хотел получить решение, которое обеспечивало бы возможность объединения неограниченного количества стилей, что немного сложнее.
Написать расширение markup очень просто. Первый шаг-создать класс, производный от MarkupExtension, и использовать атрибут MarkupExtensionReturnType, чтобы указать, что значение, возвращаемое из расширения markup, будет иметь тип Style.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Указание входных данных для расширения markup
Мы хотели бы дать пользователям нашего расширения markup простой способ указать стили, которые будут объединены. По существу, существует два способа, с помощью которых пользователь может указать входные данные для расширения markup. Пользователь может задать свойства или передать конструктору параметры. Поскольку в этом сценарии пользователю требуется возможность указать неограниченное количество стилей, мой первый подход состоял в создании конструктора, который принимает любое количество строк, используя ключевое слово “params”:
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
Моя цель состояла в том, чтобы иметь возможность написать входные данные следующим образом:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Обратите внимание на запятую, разделяющую различные клавиши стиля. К сожалению, пользовательские расширения markup не поддерживают неограниченное количество параметров конструктора, поэтому такой подход приводит к ошибке компиляции. Если бы я заранее знал, сколько стилей я хочу объединить, я мог бы использовать тот же синтаксис XAML с конструктором, принимающим желаемое количество строк:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
В качестве обходного пути я решил, что параметр конструктора должен принимать одну строку, которая определяет имена стилей, разделенные пробелами. Синтаксис не так уж плох:
private string[] resourceKeys;
public MultiStyleExtension(string inputResourceKeys)
{
if (inputResourceKeys == null)
{
throw new ArgumentNullException("inputResourceKeys");
}
this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (this.resourceKeys.Length == 0)
{
throw new ArgumentException("No input resource keys specified.");
}
}
Вычисление выходных данных расширения markup
Чтобы вычислить выходные данные расширения markup, нам нужно переопределить метод из MarkupExtension под названием “ProvideValue”. Значение, возвращаемое этим методом, будет установлено в целевой объект расширения markup.
Я начал с создания метода расширения для стиля, который знает, как объединить два стиля. Код для этого метода довольно прост:
public static void Merge(this Style style1, Style style2)
{
if (style1 == null)
{
throw new ArgumentNullException("style1");
}
if (style2 == null)
{
throw new ArgumentNullException("style2");
}
if (style1.TargetType.IsAssignableFrom(style2.TargetType))
{
style1.TargetType = style2.TargetType;
}
if (style2.BasedOn != null)
{
Merge(style1, style2.BasedOn);
}
foreach (SetterBase currentSetter in style2.Setters)
{
style1.Setters.Add(currentSetter);
}
foreach (TriggerBase currentTrigger in style2.Triggers)
{
style1.Triggers.Add(currentTrigger);
}
// This code is only needed when using DynamicResources.
foreach (object key in style2.Resources.Keys)
{
style1.Resources[key] = style2.Resources[key];
}
}
В соответствии с логикой выше, первый стиль модифицируется, чтобы включить всю информацию из второго. Если есть конфликты (например, оба стиля имеют setter для одного и того же свойства), второй стиль выигрывает. Обратите внимание, что помимо копирования стилей и триггеров, я также учитывал значения TargetType и BasedOn, а также любые ресурсы, которые может иметь второй стиль. Для TargetType объединенного стиля я использовал тот тип, который является более производным. Если второй стиль имеет стиль BasedOn, я рекурсивно объединяю его иерархию стилей. Если у него есть ресурсы, я копирую их в первый стиль. Если эти ресурсы ссылаются на использование {StaticResource}, они статически разрешаются до выполнения этого кода слияния, и поэтому нет необходимости их перемещать. Я добавил этот код на случай, если мы используем DynamicResources.
Метод расширения, показанный выше, включает следующий синтаксис:
style1.Merge(style2);
Этот синтаксис полезен при условии, что у меня есть экземпляры обоих стилей в ProvideValue. Все, что я получаю от конструктора-это список строковых ключей для этих стилей. Если бы в параметрах конструктора была поддержка параметров params, я мог бы использовать следующий синтаксис для получения фактических экземпляров стиля:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
Но это не работает. И даже если бы ограничение params не существовало, мы, вероятно, столкнулись бы с другим ограничением расширений markup, где нам пришлось бы использовать синтаксис элементов свойств вместо синтаксиса атрибутов для указания статических ресурсов, что является многословным и громоздким (я лучше объясню эту ошибку в предыдущем блоге ). И даже если бы оба эти ограничения не существовали, я все равно предпочел бы написать список стилей, используя только их имена – он короче и проще для чтения, чем StaticResource для каждого из них.
Решение состоит в том, чтобы создать StaticResourceExtension с помощью кода. Учитывая ключ стиля типа string и поставщика услуг, я могу использовать StaticResourceExtension для извлечения фактического экземпляра стиля. Вот такой синтаксис:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Теперь у нас есть все части, необходимые для написания метода ProvideValue:
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
if (currentStyle == null)
{
throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
}
resultStyle.Merge(currentStyle);
}
return resultStyle;
}
Вот полный пример использования расширения MultiStyle markup:
<Window.Resources>
<Style TargetType="Button" x:Key="SmallButtonStyle">
<Setter Property="Width" Value="120" />
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style TargetType="Button" x:Key="GreenButtonStyle">
<Setter Property="Foreground" Value="Green" />
</Style>
<Style TargetType="Button" x:Key="BoldButtonStyle">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</Window.Resources>
<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />
Но вы можете вытянуться из другого.. взгляните на свойство BasedOn
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="3" />
</Style>
<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
WPF/XAML не обеспечивает эту функциональность изначально, но она обеспечивает расширяемость, чтобы позволить вам делать то, что вы хотите.
Мы столкнулись с той же самой потребностью и в конечном итоге создали наше собственное расширение XAML Markup (которое мы назвали "MergedStylesExtension"), чтобы позволить нам создать новый стиль из двух других стилей (которые, если потребуется, возможно, можно будет использовать несколько раз подряд, чтобы наследовать еще больше стилей).
Из-за ошибки WPF/XAML нам нужно использовать синтаксис элемента свойства, чтобы использовать его, но в остальном он, кажется, работает нормально. E.g.,
<Button
Content="This is an example of a button using two merged styles">
<Button.Style>
<ext:MergedStyles
BasedOn="{StaticResource FirstStyle}"
MergeStyle="{StaticResource SecondStyle}"/>
</Button.Style>
</Button>
Я недавно писал об этом здесь: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
Это можно сделать, создав вспомогательный класс для использования и переноса ваших стилей. CompoundStyle упомянутое здесь показывает, как это сделать. Есть несколько способов, но самый простой-это сделать следующее:
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
Надеюсь, это поможет.
Используйте AttachedProperty для установки нескольких стилей, таких как следующий код:
public class Css
{
public static string GetClass(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(ClassProperty);
}
public static void SetClass(DependencyObject element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(ClassProperty, value);
}
public static readonly DependencyProperty ClassProperty =
DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css),
new PropertyMetadata(null, OnClassChanged));
private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ui = d as FrameworkElement;
Style newStyle = new Style();
if (e.NewValue != null)
{
var names = e.NewValue as string;
var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in arr)
{
Style style = ui.FindResource(name) as Style;
foreach (var setter in style.Setters)
{
newStyle.Setters.Add(setter);
}
foreach (var trigger in style.Triggers)
{
newStyle.Triggers.Add(trigger);
}
}
}
ui.Style = newStyle;
}
}
Использование:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:style_a_class_like_css"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="325">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Red" >
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="TextBlock" x:Key="Green" >
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style TargetType="TextBlock" x:Key="Size18" >
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="6"/>
</Style>
<Style TargetType="TextBlock" x:Key="Bold" >
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<StackPanel>
<Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
<Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
<Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>
</StackPanel>
</Window>
Результат:
если вы не касаетесь каких-либо конкретных свойств, вы можете получить все базовые и общие свойства для стиля, целевым типом которого будет FrameworkElement. затем вы можете создать определенные ароматы для каждого целевого типа, который вам нужен, без необходимости повторного копирования всех этих общих свойств.
Вероятно, вы можете получить нечто подобное, применив это к коллекции элементов с помощью StyleSelector, я использовал это для решения аналогичной проблемы при использовании различных стилей на TreeViewItems в зависимости от типа связанного объекта в дереве. Возможно, вам придется немного изменить класс ниже, чтобы приспособиться к вашему конкретному подходу, но, надеюсь, это поможет вам начать работу
public class MyTreeStyleSelector : StyleSelector
{
public Style DefaultStyle
{
get;
set;
}
public Style NewStyle
{
get;
set;
}
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);
//apply to only the first element in the container (new node)
if (item == ctrl.Items[0])
{
return NewStyle;
}
else
{
//otherwise use the default style
return DefaultStyle;
}
}
}
Затем вы применяете это как так
<TreeView>
<TreeView.ItemContainerStyleSelector
<myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
NewStyle="{StaticResource NewItemStyle}" />
</TreeView.ItemContainerStyleSelector>
</TreeView>
Иногда вы можете приблизиться к этому, вложив панели. Допустим, у вас есть стиль, который меняет передний план, а другой меняет FontSize, вы можете применить последний к TextBlock и поместить его в сетку, где его стиль является первым. Это может помочь и может быть самым простым способом в некоторых случаях, хотя это не решит всех проблем.
При переопределении SelectStyle вы можете получить свойство GroupBy через отражение, как показано ниже:
public override Style SelectStyle(object item, DependencyObject container)
{
PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
{
return this.TitleStyle;
}
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
{
return this.DateStyle;
}
return null;
}
Если вы пытаетесь применить уникальный стиль только к одному элементу в качестве дополнения к базовому стилю, есть совершенно другой способ сделать это, который является IMHO гораздо лучше для читаемого и поддерживаемого кода.
Очень часто требуется настроить параметры для каждого отдельного элемента. Определение стилей словаря только для использования на одном элементе является чрезвычайно громоздким для поддержания или осмысления. Чтобы избежать создания стилей только для одноразовых настроек элементов, прочитайте мой ответ на мой собственный вопрос здесь здесь:
https://stackoverflow.com/a/54497665/1402498