I have run across a problem when overriding default styles in an MVVM . I am trying to change the default foreground value of all of the text in my application. This works until I have Text Blocks in UserControls. These Text Blocks do not inherit from the default, unlike the other controls such as Labels.
I have created a small example for this.
I have a generic Resource dictionary declared. This is holding the default styles.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1.Resources">
<Style TargetType="Label">
<Setter Property="Foreground"
Value="Blue" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground"
Value="Red" />
</Style>
This resource dictionary is merged into the MainWindow.
<Window>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type vm:UserControlViewModel}">
<v:UserControl1 />
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<Label>Main Window Label</Label>
<TextBlock Grid.Column="1">Main Window Text Block</TextBlock>
<ContentPresenter Content="{Binding Content}" />
</Grid>
</Window>
In my simple example I have a generic MainViewModel set to the DataContext of the MainWindow.
public class MainViewModel
{
public UserControlViewModel Content { get; set; }
public MainViewModel()
{
Content = new UserControlViewModel();
}
}
The user control viewmodel is simply a shell with no properties that need to be displayed. However the user control itself is just a simple label and text block like the Main Window
<UserControl>
<Grid>
<Label>UserControl Label</Label>
<TextBlock >User Control Text Block</TextBlock>
</Grid>
</UserControl>
The weird part is the override for the Label works properly and the Text Block does not. This is the picture of the end result.
Why does the Text Block in the user control not use the default style that is applied to the Text Block?
Related
In short, the question title says it all. For those that want more detail, here is the crux of my problem: I need to apply a custom ControlTemplate to the DataGridColumnHeader elements in my DataGrid control, but I also need to style them differently, depending on the cell data nearest the header. However, when I set both the ContentTemplateSelector and Template properties on a DataGridColumnHeader element, the DataTemplateSelector that is set as the value of the ContentTemplateSelector property is not called. Commenting out the Template property setting confirms this to be the case, as the DataTemplateSelector element will now be called.
Yes, I know that you guys love to see some code, but I have completely templated the whole DataGrid control to look like Excel, so as you can imagine, I have far too much code to display here. But just to please you code hungry devs, I've recreated my problem in a much simpler example... let's first see the XAML:
<Window x:Class="WpfApp1.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:WpfApp1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.Items>
<System:String>One</System:String>
<System:String>Two</System:String>
<System:String>Three</System:String>
</DataGrid.Items>
<DataGrid.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
Now the most simple DataTemplateSelector class:
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public class StringDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Debugger.Break();
return null;
}
}
}
In the XAML, we see a DataGrid, with just one DataGridTemplateColumn and three string values, one on each row, and some resources. There is a Style for the DataGridColumnHeader element in the Resource section, with the most simple ControlTemplate set up for it, that only includes the required named parts from the default ControlTemplate.
If you run the application as it is, then it will NOT currently break at the Debugger.Break() method in the StringDataTemplateSelector class. This is unexpected. If you now comment out the setting of the Template property in the Style and run the application again, then you will now see that program execution will now break at the Debugger.Break() method, as expected.
Further information:
In the Remarks section of the ContentControl.ContentTemplateSelector Property page of MSDN, it states that
If both the ContentTemplateSelector and the ContentTemplate properties are set, then this property is ignored.
However, it does not mention the Template property and there is also no mention of this on the Control.Template Property page on MSDN.
Furthermore, I tried this same setup using a simple Button control and can confirm that setting both the ContentTemplateSelector and the ContentTemplate properties on that does NOT stop the StringDataTemplateSelector class from being called:
<ItemsControl>
<ItemsControl.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type Button}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Stroke="Red" StrokeThickness="1" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding Height}" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<Button Content="One" />
<Button Content="Two" />
<Button Content="Three" />
</ItemsControl>
So, what I'm after is a way to apply a custom ControlTemplate element to the DataGridColumnHeader objects, yet still be able to have the DataTemplateSelector class called during the rendering process.
add a content presenter in your controltemplate?
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
<ContentPresenter></ContentPresenter>
</Grid>
</ControlTemplate>
Inside my window xaml:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush>
<VisualBrush.Visual>
<Image Source="{Binding Path=ImageSource,Converter={StaticResource imgconverter}}">
<Image.BitmapEffect>
<BlurBitmapEffect KernelType="Box" />
</Image.BitmapEffect>
</Image>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Style>
</Grid.Style>
</Grid>
(I added to window resources this converter)
I would like to add background image with blur effect to this grid (with MVVM pattern) but my property never called in my viewmodel. If i use just converter with "Path=." the converter going to work but I have to use static ImageSource in conveter because if i add any object type for ImageSource (to path) (BitmapImage,ImageSource,..,etc), the converter will not call. (I tried to use UpdateSourceTrigger with PropertyChanged value but this solution didn't help to me.) The converter just an unwanted solution because this is the only one way to set my background correctly because my ImageSouce property has wanted value but its not works without converter and unfortunately the converter will not work too if i add any path to binding.
Here is my property inside the ViewModel:
private ImageSource _imageSource;
public ImageSource ImageSource
{
get
{
return _imageSource;
}
set
{
_imageSource = value;
OnPropertyChanged();
}
}
Any idea to set my background image with Blur effect correctly with MVVM pattern and without use uri path? (i dont want to save the image to physical storage)
A VisualBrush is not part of the element tree so it doesn't inherit the DataContext of the Grid.
But you could define the Image as a resource and bind its Source property to a property of the parent window using an {x:Reference}. This should work:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300" x:Name="win">
<Window.Resources>
<local:imgconverter x:Key="imgconverter" />
</Window.Resources>
<Grid>
<Grid.Resources>
<Image x:Key="img" Source="{Binding Path=DataContext.ImageSource,Converter={StaticResource imgconverter}, Source={x:Reference win}}">
<Image.BitmapEffect>
<BlurBitmapEffect KernelType="Box" />
</Image.BitmapEffect>
</Image>
</Grid.Resources>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Visual="{StaticResource img}" />
</Setter.Value>
</Setter>
</Style>
</Grid.Style>
<TextBlock Text="..." />
</Grid>
</Window>
I have a custom control which has a dependency property called ViewModel which value is shown inside a ContentPresenter. I have a DataTemplate for each type of ViewModel. Each template allows the user to make a selection in a different way and I need to handle that selection event in the custom control code behind.
<Style TargetType="{x:Type local:MyCustomControl}">
<Style.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type local:ViewModelOne}">
<!-- how to handle this event? -->
<ListBox
MouseDoubleClick="ListBox_MouseDoubleClick"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelTwo}">
<!-- this ListBox has another style, but event should
be handled the same way -->
<ListBox
MouseDoubleClick="ListBox_MouseDoubleClick"/>
</DataTemplate>
<!-- more templates here -->
</ResourceDictionary>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<ContentPresenter Content="{TemplateBinding ViewModel}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit:
Here's the code behind of the custom control with the method I would like to be called when something in the ListBox is double clicked:
public class MyCustomControl : Control
{
// how to attach ListBox MouseDoubleClick event to this method?
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DoMagic(((ListBox)sender).SelectedItem);
}
}
Are these DataTemplates defined in a resource dictionary?
if so, you can use the attached behaviors.
If they are defined in a MyWindow or a MyUserControl XAML then you can defined them the code behind i.e. MyWindow.xaml.cs or MyUserControl.xaml.cs
I am setting some global styles for TextBox, ComboBox, TextBlock, etc. in my App.xaml file. I want these styles to flow down through the visual tree for a consistent look and that is what they are doing.
However, I am using a Ribbon in part of my UI. The style customizations are completely throwing off the appearance of the Ribbon. The ribbon sits at the same level as many other UI elements so is there a way to just reset the style for the ribbon and its visual children
I don't think that you can change this wpf behaviour.
But you can try to contain all global styles to specific resource dictionary and use it in all other VisualTree elements.
Or you can try something like this:
<Window ...>
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="Red" />
</Style>
</Window.Resources>
<DockPanel LastChildFill="True">
<Button x:Name="button1" Content="button" DockPanel.Dock="Top" />
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
</Style>
</StackPanel.Resources>
<Button x:Name="button2" Content="lala" />
</StackPanel>
</DockPanel>
</Window>
Element with name "button2" will be default.
How can I change the style -ONCE- for the scrollbars shown by all controls (listbox, treeview, scrollbarviewer, richtextbox, etc...)?
If you will define your Style for a control with no x:Key attribute, it will be applied for all instances of that control.
Try like this:
<Window.Resources>
<Style TargetType="ScrollBar">
<Setter Property="Background" Value="Red"/>
</Style>
</Window.Resources>
<Grid>
<TextBox Margin="24,12,0,0" VerticalScrollBarVisibility="Visible" AcceptsReturn="True" Height="28" VerticalAlignment="Top" HorizontalAlignment="Left" Width="89" />
<ScrollBar Name="scroll" HorizontalAlignment="Right" />
</Grid>
Here you can see that the Style is defined for ScrollBar control and have no x:Key attribute defined so it gets applied to the each ScrollBar instance within Window. Like ScrollBar of TextBox and ScrollBar named scroll also.
Hope this helps!
Thanks. Finally the problem was solved by setting the style in the Themes/Generic.xaml and (because of custom controls existing in another assembly with they respective resources merged) adding the following to App.xaml...
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Generic.xaml"/>
<ResourceDictionary Source="MyCtls/MyRes.xaml" />
</ResourceDictionary>
</Application.Resources>
The key point is to merge the Generic.xaml file.
Also, if the resource dictionary is in another assembly, it must be referenced as...
<ResourceDictionary Source="pack://application:,,,/OtherAssembly;component/MyCtls/MyRes.xaml"/>