how to apply style to wpf control in seperate class programmatically - c#

I have a control that inherits from RadioButton, and upon creation of the control I want to set its Style to a ResourceDictionary. I have tested that this works when making the control in the XAML and from MainWindow.xaml.cs. However, setting the Style from the custom class does not work and the style is not applied.
Here is the code from the class:
using System.Linq;
using System.IO;
using System.Windows;
namespace TextEditor
{
internal class TopbarItem : RadioButton
{
private static MainWindow window = (MainWindow)App.Current.MainWindow;
private string Path = "";
public string Title { get; set; }
public string Text { get; set; }
public TopbarItem(string path)
{
Path = path;
Title = Path.Split(#"\").Last();
Content = Title;
IsChecked = true;
Style = (Style)window.Resources["TextViewTheme"];
Click += new System.Windows.RoutedEventHandler(window.TabClicked);
window.Topbar.Children.Add(this);
UpdateText();
}
public void UpdateText()
{
StreamReader read = new StreamReader(Path);
Text = read.ReadToEnd();
read.Close();
if (Text.Length == 0) { Text = "Hello World!"; }
}
}
}
And here is the ResourceDictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style BasedOn="{StaticResource {x:Type ToggleButton}}" TargetType="{x:Type RadioButton}" x:Key="TextViewTheme">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid Background="{TemplateBinding Background}" Width="100" Height="30" VerticalAlignment="Bottom" HorizontalAlignment="Left">
<TextBlock Text="{TemplateBinding Property=Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontSize="10"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="#111"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#c8c8ff"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#c8c8ff"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
Sorry if I left anything out, I'm new to WPF.

Related

How to bind the IsEnabled Property of a WPF ListBoxItem to a Property of its Source Item?

I have a ListBox with an ObservableCollection as ItemSource:
public partial class HomePage : Page
{
public ObservableCollection<DaemonFile> Files { get; private set; }
private readonly MainWindow MainWindow;
public HomePage(MainWindow MainWindow)
{
Files = new ObservableCollection<DaemonFile>();
this.MainWindow = MainWindow;
InitializeComponent();
ListOfFiles.ItemsSource = Files;
}
...
}
I have the following properties in my DaemonFile:
public class DaemonFile : INotifyPropertyChanged
{
public enum ProcessStates : int
{
CREATED = 0,
CONVERTED = 1,
UPLOADING = 2,
FINISHED = 3
}
private ProcessStates ProcessStateValue = ProcessStates.CREATED;
public ProcessStates ProcessState
{
get { return this.ProcessStateValue; }
set
{
if (value != this.ProcessStateValue)
{
this.ProcessStateValue = value;
NotifyPropertyChanged();
}
}
}
...
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
mainWindow.WriteLine("Property " + propertyName + " Changed!");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
} else
{
mainWindow.WriteLine("Cant fire event!");
}
}
}
The ProcessState gets updated asynchronously after the conversion of the file has finished.
I figured I need to bind this property and put it into a setter for IsEnabled.
<Style x:Key="Item" TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}">
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessState}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
How can I achieve that whenever a DaemonFile changes its ProcessState to 1 or higher the corresponding ListBoxItem switches to enabled?
I don't want a user to be able to upload a file before it has finished converting
I could also add an isEnabled property to my DaemonFile to simplify things a little. That doesn't solve my binding problem though.
You do not need that ControlTemplate.
A simple DataTrigger should work:
<Style x:Key="ItemStyle" TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessState}" Value="0">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Trigers>
</Style>
Just in case:
<ListBox x:Name="ListOfFiles"
ItemContainerStyle="{StaticResource ItemStyle}" ...>

How to bind to another's object property with XAML Style Template?

Let's say I have the following class:
public class MyClass : System.Windows.FrameworkElement
{
public static readonly DependencyProperty HasFocusProperty = DependencyProperty.RegisterAttached("HasFocus", typeof(bool), typeof(MyClass), new PropertyMetadata(default(bool)));
public bool HasFocus
{
get => (bool)GetValue(HasFocusProperty);
set => SetValue(HasFocusProperty, value);
}
public System.Windows.Controls.TextBox TextBox { get; set; }
}
I want to change some UI properties of TextBox via XAML Template Trigger based on the property HasFocus, so I do the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win="clr-namespace:System.Windows.Controls">
<Style TargetType="{x:Type win:TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type win:TextBox}">
<ControlTemplate.Triggers>
<Trigger Property="MyClass.HasFocus" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Red" />
<Setter TargetName="Border" Property="BorderThickness" Value="2" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
However, the style is not applied when setting HasFocus = true.
Within the properties of TextBox, I can see that the trigger is registered. If I change <Trigger Property="MyClass.HasFocus" Value="True"> to <Trigger Property="MyClass.HasFocus" Value="False">, my style is applied initially. So I think my XAML definition is okay.
Any ideas how to solve this?
An element in a template that is applied to a TextBox cannot bind to a property of a MyClass, unless there is a MyClass element to bind to somewhere in the visual tree.
If you want to be able to set a custom HasFocus property of a TextBox, you should create an attached property:
public class FocusExtensions
{
public static readonly DependencyProperty SetHasFocusProperty = DependencyProperty.RegisterAttached(
"HasFocus",
typeof(bool),
typeof(FocusExtensions),
new FrameworkPropertyMetadata(false)
);
public static void SetHasFocus(TextBox element, bool value)
{
element.SetValue(SetHasFocusProperty, value);
}
public static bool GetHasFocus(TextBox element)
{
return (bool)element.GetValue(SetHasFocusProperty);
}
}
It can be set for any TextBox element:
<TextBox local:FocusExtensions.HasFocus="True">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="local:FocusExtensions.HasFocus" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Change windows style on click WPF

I want to change style on click, but simply cant find any way to do it.
Here is styles
<Style x:Key="CustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="MinHeight" Value="46"/>
<!--CaptionHeight + ResizeBorderThickness * 2-->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="7"/>
<Setter Property="Foreground" Value="DarkRed"/>
<Setter Property="Template" Value="{StaticResource WindowTemplate}"/>
</Style>
<!--the red style window-->
<Style x:Key="RedWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowStyle" Value="None"/>
<Setter Property="AllowsTransparency" Value="True"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="MinHeight" Value="46"/>
<Setter Property="Background" Value="white"/>
<Setter Property="BorderBrush" Value="DarkRed"/>
<Setter Property="BorderThickness" Value="7"/>
<Setter Property="Foreground" Value="DarkGray"/>
<Setter Property="Template" Value="{StaticResource WindowTemplate}"/>
</Style>
I want to change it in MainWindow.xaml.cs
private void button_Click(object sender, RoutedEventArgs e)
{
this.Style ?? // dont know what to do
}
Please help
I would probably bind this in my style. For example:
MainWindow.xaml:
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="local:MainWindow">
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeProperty}" Value="True">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button HorizontalAlignment="Center" VerticalAlignment="Center" Click="Button_Click">Click Me, Yo!</Button>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp2
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _someProperty;
public bool SomeProperty
{
get { return _someProperty; }
set
{
_someProperty = value;
OnPropertyChanged();
}
}
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeProperty = !SomeProperty;
}
}
}
Essentially, you derive the state of your window from some property in your ViewModel, using a converter if needed.
Of course, if you want a direct solution, you could just use FindResource:
this.Style = this.FindResource("MyLocalStyleResourceName") as Style;
This is described here.

WPF caliburn Screen and Tab control

At the moment I having working tabs and they open and close properly. I'm trying to implement a Close All functionality and a Close all but this tab functionality, was wondering how abouts do I do that? The Tabs are Initialized in my ShellViewModel.
Current TabsView.xaml
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel Name="Panel" Background="#88DDDDDD" SnapsToDevicePixels="True" Orientation="Horizontal" Margin="1 0" cal:Message.Attach="[Event MouseDown] = [Action Show($dataContext)]">
<TextBlock Text="{Binding DisplayName}" VerticalAlignment="Center" TextAlignment="Right" FontWeight="Bold" >
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Close All Tabs" cal:Message.Attach="[Event Click] = [Action CloseTabs($this)]"/>
<MenuItem Header="Close All But This" cal:Message.Attach="[Event Click] = [Action CloseAllButThis]"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<Button Background="Transparent" cal:Message.Attach="[Click] = [Close($this)]" BorderThickness="0" VerticalAlignment="Center">
<ContentControl ContentTemplate="{StaticResource Icons.CloseButtonSmall}" Background="#900" Width="10" Height="10" Margin="3" VerticalAlignment="Center"/>
</Button>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Panel" Property="Background" Value="#DDD"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#093"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
TabsViewModel.cs
private readonly IEventAggregator _events;
public List<IScreen> Items { get; private set; }
public WorkTabsViewModel(IEventAggregator events)
{
_events = events;
_events.Subscribe(this);
}
public void Close(IScreen tab)
{
if (tab.DisplayName == "Settings")
{
var settingsViewModel = tab as SettingsViewModel;
if (settingsViewModel != null)
{
tab.TryClose();
}
}
else
{
tab.TryClose();
}
}
public void Show(IScreen screen)
{
_events.PublishOnUIThread(new ShowTabEvent(screen));
}
public void Handle(ScreenChangeEvent screenChangeEvent)
{
Items = screenChangeEvent.Tabs.Where(x => Array.IndexOf(HiddenTabs, x.GetType().Name) < 0).ToList();
NotifyOfPropertyChange(() => Items);
}
In your ViewModel you can do the following:
public class WorkTabsViewModelpublic extends Conductor<IScreen>.Collection.OneActive
{
// ....
void CloseAll() {
foreach (IScreen tab in Items)
{
tab.TryClose();
}
}
// ....
}
Then in your view you can add a button that will call that method when clicked
<Button x:Name="CloseAll">Close All Tabs</Button>
As long as your ViewModel is extends a Conductor<IScreen>.Collection.OneActive (or something similar), this will loop through all of your open tabs and try to close them.

Setting Label.Content to Control on DataTrigger

recently I've tried to make label style which would allow to display image or textblock depending on property being set or not. I've bound proper objects to labels' DataContext and prepared reusable style for these labels. Default content is textblock with Name as its text but if IsIconSet property is true, then content would change into image with corresponding IconPath as source.
Similar approach works perfectly with label's properties like background or cursor but in described scenario it breaks up when IsIconSet has the same value in both instances. Then it displays nothing for first label and correct textblock/image for second label.
I've tried to attach converter to Name and IconPath bindings in style in order to check what value is being passed but it seems that it isn't even invoked on first label.
Has anyone managed to do something similar? Am I missing something fundamental? Or maybe there is another approach for such behaviour?
Any help will be appreciate.
Simplified code:
MainWindow
<StackPanel DataContext="{Binding First}">
<Label Style="{StaticResource LabelStyle}" />
</StackPanel>
<StackPanel DataContext="{Binding Second}">
<Label Style="{StaticResource LabelStyle}" />
</StackPanel>
Style
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content">
<Setter.Value>
<TextBlock Text="{Binding Name}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="Content">
<Setter.Value>
<Image Source="{Binding IconPath}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Classes
public class ViewModel : INotifyPropertyChanged
{
private LabelClass _first;
private LabelClass _second;
public LabelClass First
{
get => _first;
set
{
_first = value;
OnPropertyChanged();
}
}
public LabelClass Second
{
get => _second;
set
{
_second = value;
OnPropertyChanged();
}
}
public ViewModel()
{
First = new LabelClass("First", "Resources/first.png");
Second = new LabelClass("Second", "Resources/second.png");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class LabelClass : INotifyPropertyChanged
{
private string _name;
private string _iconPath;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string IconPath
{
get => _iconPath;
set
{
_iconPath = value;
OnPropertyChanged();
OnPropertyChanged("IsIconSet");
}
}
public bool IsIconSet => !string.IsNullOrEmpty(IconPath);
public LabelClass(string name, string iconPath = null)
{
Name = name;
IconPath = iconPath;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
LabelStyle can be used by multiple Labels, but TextBlock and Image from Content setter are created just once, there is only one instance for all labels, but it cannot be displayed in multiple places. so it is displayed in only one.
to fix the issue use ContentTemplate, like shown below.
<Setter Property="Content" Value="{Binding}"/> line means that entire DataContext is considered as Label Content. Is is necessary for bindings in ContentTemplate
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding IconPath}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
alternatively, convert TextBlock and Image to non-shared resources:
<Image Source="{Binding IconPath}" x:Key="Img" x:Shared="False"/>
<TextBlock Text="{Binding Name}" x:Key="Txt" x:Shared="False"/>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Content" Value="{StaticResource Txt}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsIconSet}" Value="True">
<Setter Property="Content" Value="{StaticResource Img}"/>
</DataTrigger>
</Style.Triggers>
</Style>

Categories