I'm new to WPF, but searching internet for some days I couldn't figure out my problem.
After I programmatically change Foreground property, IsMouseOver trigger doesn't work. Please be tolerant and thank in advance :)
<Style x:Key="ZizaMenuItem" TargetType="{x:Type Button}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="5,0,5,0"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Label FontSize="14" Content="{TemplateBinding Content}" Name="ZizaMenuItemText" />
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ZizaMenuItemText" Property="Foreground" Value="#ff0000"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<StackPanel Height="30" Name="ZizaMenu" Orientation="Horizontal" Margin="0,12,0,0" VerticalAlignment="Top">
<Label Content="ZIZA" FontSize="11" FontWeight="Bold" Foreground="Black" Height="25" Margin="20,0,10,0" />
<Button Name="ZizaMenuInteresting" Click="ZizaMenuItemClicked" Content="ИНТЕРЕСНОЕ" Style="{StaticResource ZizaMenuItem}" />
<Button Name="ZizaMenuBest" Click="ZizaMenuItemClicked" Content="ЛУЧШЕЕ" Style="{StaticResource ZizaMenuItem}" />
<Button Name="ZizaMenuAuto" Click="ZizaMenuItemClicked" Content="АВТО" Style="{StaticResource ZizaMenuItem}" />
</StackPanel>
private void ZizaMenuItemClicked(object sender, RoutedEventArgs e)
{
// get label object from template
Button zizaMenuItem = (Button)sender;
Label zizaMenuItemText = (Label)zizaMenuItem.Template.FindName("ZizaMenuItemText", zizaMenuItem);
// set Foreground color for all buttons in menu
foreach (var item in ZizaMenu.Children)
if (item is Button)
((Label)(item as Button).Template.FindName("ZizaMenuItemText", (item as Button))).Foreground = Brushes.Black;
// set desired color to clicked button label
zizaMenuItemText.Foreground = new SolidColorBrush(Color.FromRgb(102, 206, 245));
}
That is horrible code, do not mess with controls inside control templates, ever. Template.FindName is something only the control that is being templated should call internally to get its parts, and only those, everything else should be considered uncertain.
If you need to change a property template bind it, and then bind or set said property on the instance. In terms of precedence you need to make sure not to create a local value which overrides the triggers (that is what you did). You can use a Style and Setter on the Label to bind the default Foreground.
<Label.Style>
<Style TargetType="Label">
<Setter Property="Foreground" Value="{TemplateBinding Foreground}"/>
</Style>
</Label.Style>
Now you just need to set the Foreground of the Button itself, the Trigger should still internally have precedence over that Setter.
It has to do with dependency property value precedence. Local values have higher precedence than template triggers.
For more information read this: http://msdn.microsoft.com/en-us/library/ms743230.aspx
Related
I need a text box with a button in it,it must display a default value but should still allow the user to type into a text that i need to store in my ViewModel property.
The button should reset the value to the default one.
I got few issues with this implementation:
When the user type into the textbox i would expect the bound property in my viewModel to update accordingly, but seems there is no binding anymore. (Binding is set two way)
(the binding and the DataContext is correct, as on load is displaying the value set from the ViewModel)
Once i type into the box and hit the revert button the text is assign to the property as expected, but the text box still display he same value type by the user.
Each time i move across tabs o click another control, the button responsible for revert the text back, needs to be clicked twice (looks like a focus issue) as once the focus is in the text box all is working normally.
I have created a Generic.xaml were i have defined the control template.
<Style x:Key="{x:Type local:RememberValue}" TargetType="{x:Type local:RememberValue}">
<Setter Property="Background" Value="{StaticResource RemeberValue_Background}" />
<Setter Property="BorderBrush" Value="{StaticResource RemeberValue_Border}" />
<Setter Property="Foreground" Value="{StaticResource RemeberValue_Foreground}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Focusable" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:RememberValue}">
<Grid x:Name="LayoutGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<baseControlUi:IconButton
Grid.Column="0"
Height="22"
Grid.ZIndex="1"
Margin="0"
EllipseDiameter="19"
Focusable="True"
Visibility="{Binding ElementName=RememberValueControl, Path=IsDifferentValue, Converter={StaticResource BooleanToVisibilityConverter}}"
ButtonCommand="{TemplateBinding RevertCommand}"
ButtonIcon="{StaticResource RevertIcon}" />
<TextBox
Grid.ZIndex="0"
Foreground="{StaticResource RemeberValue_Foreground}"
Text="{TemplateBinding DisplayText}"
HorizontalAlignment="Stretch"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This is the usage in the View.
<StackPanel Width="400">
<remebervalue:RememberValue
DisplayText="{Binding DisplayText, UpdateSourceTrigger=PropertyChanged}"
DefaultValue="{Binding DefaultText, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left" Width="400" />
</StackPanel>
the code behind of RemeberValue.cs ha DP registered for the DisplayText and the DefaultText
public static readonly DependencyProperty DisplayTextProperty =
DependencyProperty.Register(nameof(DisplayText), typeof(string), typeof(RememberValue), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDisplayText_Changed));
public RememberValue()
{
RevertCommand = new SimpleCommand(Revert);
}
private void Revert()
{
DisplayText = DefaultValue;
}
public string DisplayText
{
get => (string)GetValue(DisplayTextProperty);
set => SetValue(DisplayTextProperty, value);
}
private static void OnDisplayText_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RememberValue RememberValue = d as RememberValue;
}
Partial answer
First point: I believe you are mistaken in writing "Binding is set two way", as you are using TemplateBinding, which is always one-way. You should replace it with
Binding DisplayText, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay
Second point: fixed by the above
Third point: different issue, needs to be addressed in a different question.
I have this ResourceDictionary
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="MainMenuLabelStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property ="IsMouseOver" Value="True">
<Setter Property= "Foreground" Value="White"/>
<Setter Property= "FontSize" Value="18"/>
<Setter Property= "FontFamily" Value="Arial"/>
</Trigger>
</Style.Triggers>
</Style>
If I want change the font size or color, what can I do ? This code doesn't work .
Application.Current.Resources("MainMenuLabelStyle") = 25
This is the xaml
<TextBlock Text="Uscita" Grid.Row="1" Grid.Column="1" TextAlignment="Left" Margin="4" TextWrapping="Wrap" Style="{DynamicResource MainMenuLabelStyle}">
Just before a style is used for the first time in a WPF application, it is sealed for performance reasons and it is not possible to modify it anymore. You can read it on MSDN.
So, if you want to change your style, you have to options. The first one (the easiest one) is to declare as many styles as you need and put them in your ResourceDictionary.
The second solution is to consider that a Setter is a DependencyObject, so you can bind its dependency properties. In this case your style will become:
<Style x:Key="MainMenuLabelStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag.Foreground, TargetNullValue=Red, FallbackValue=Red}" />
<Setter Property="FontSize" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag.FontSize, TargetNullValue=18, FallbackValue=18}" />
<Setter Property="FontFamily" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Tag.FontFamily, TargetNullValue=Arial, FallbackValue=Arial}" />
</Trigger>
</Style.Triggers>
</Style>
Now you can change the style just by setting the Tag property of every TextBlock control:
<StackPanel>
<TextBlock Text="Uscita" TextAlignment="Left" Margin="4" TextWrapping="Wrap" Style="{DynamicResource MainMenuLabelStyle}" />
<TextBlock Text="Uscita" TextAlignment="Left" Margin="4" TextWrapping="Wrap" Style="{DynamicResource MainMenuLabelStyle}">
<TextBlock.Tag>
<local:StyleConfig FontSize="50" FontFamily="Tahoma" Foreground="Orange" />
</TextBlock.Tag>
</TextBlock>
</StackPanel>
As you can see the first TextBlock will use the style as it was declared. On the other side, the second TextBlock will use a modified version of the original style.
Of course, in order to make this option work correctly, you must create a class (StyleConfig in my sample), which could be something like this:
public class StyleConfig
{
public string Foreground { get; set; }
public string FontSize { get; set; }
public string FontFamily { get; set; }
}
I hope it can help you.
In your code:
Application.Current.Resources("MainMenuLabelStyle") = 25
1) Wrong syntax. Application.Current.Resources["MainMenuLabelStyle"]
2) Application.Current.Resources["MainMenuLabelStyle"] this code will return object with type Style, not style property Font Size.
You can create new Style and replace it in ResourceDictionary.
I have in my XAML the following lines as Window.Resources:
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="pics/greenbutton.png" />
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="White" />
</Style>
In my Window are several ComboBoxes where this is good. But I have one, where it is disturbing, so I wanted to set the style to null. I already put a Style="{x:Null}" inside the XAML-ComboBox. That gives the ComboBox itself a good view, but not the open Box (i.e. the ComboBoxItems). I use a DataBinding inside the Code-Behind, so how can I delete the window-style for the ComboBoxItems?
You should add to ComboBox resources empty style with target type ComboBoxItem.
You can do this in the XAML like this:
<ComboBox x:Name="myComboBox" ...>
<ComboBox.Resources>
<Style TargetType="ComboBoxItem">
</Style>
</ComboBox.Resources>
...
</ComboBox>
Or you can do this in the code-behind using following code:
myComboBox.Resources.Add(typeof(ComboBoxItem), new Style(typeof(ComboBoxItem)));
I'm having an issue with a TabControl where I manage (in some special cases) to get two tabs headers selected (only one body showing afaik), and I can't change the selected tab.
Selected tabs have bold header text.
In this image, "Ämnesinformation" and "R43" are both selected.
My application is structured as follows:
I have some views:
MainView: The main view, contains the TabControl which only contains one item in the image.
SubstanceTabsView: One of these for every tab in MainView.
SubstanceView and ClassificationView: the first is used for the "Ämnesinformation", of which there is only one per substance. The second can have multiple instances, like "R43", "R12" etc.
I also have some viewModels:
MainViewModel: The VM for the MainView.
SubstanceTabsViewModel: The VM for the SubstanceTabsView, contains a set of IViewModels
SubstanceViewModel, ClassificationViewModel: both implement IViewModel, are VMs for SubstanceView and ClassificationView
Some relevant xaml code:
Here's the tabcontrol in MainView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}" >
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" >
</TextBlock>
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,-4,0"
Background="{Binding Path=HeaderBackground}"
BorderBrush="#A0A0A0"
BorderThickness="1,1,1,1"
CornerRadius="3,10,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Panel.ZIndex" Value="100" />
<Setter TargetName="Border" Property="Background" Value="{Binding HeaderBackground}" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="Yellow" />
<Setter TargetName="Border" Property="BorderBrush" Value="Black" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter Property="Foreground" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type localViewModels:SubstanceTabsViewModel}">
<localViews:SubstanceTabsView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's how I control the connection between different views and viewmodels in SubstanceTabsView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" />
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type localViewModels:ClassificationViewModel}">
<localViews:ClassificationView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:SubstanceViewModel}">
<localViews:SubstanceView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's the code for SubstanceTabsViewModel.cs which controls the second level tabs, the setter for the selectedTab controls some logic which asks the user about changing from an unsaved tab:
private IViewModel selectedTab;
public IViewModel SelectedTab
{
get
{
return selectedTab;
}
set
{
MessageBoxResult rsltMessageBox = MessageBoxResult.Yes;
if (selectedTab != null && selectedTab.SaveNeeded() && selectedTab.Id != 0 && value != null && selectedTab is ClassificationViewModel)
{
rsltMessageBox = notifyUserService.Ask("Bedömning är ändrad men ej sparad vill du verkligen lämna fliken?", "Bedömning ändrad");
}
if (rsltMessageBox == MessageBoxResult.Yes)
{
selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
private ObservableCollection<IViewModel> tabs;
public ObservableCollection<IViewModel> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
Some things my investigations have resulted in: If I don't do the notifyUserService call (which results in a messagebox.show()), there is no problem, only one tab is selected. If I look at the SelectedItem of the TabControl, it is only one item, the item it "should" be in my situation.
I finally found someone else having a similar problem, as described here**, "Displaying a message box causes a nested message pump; which means that almost all processing resumes. Of course, we are in the middle of trying to change the selected item, so this can cause all sorts of out-of-order or reentrancy problems. This class of problems is difficult to fix, and we are not going to be able to fix this in our next release." So the problem was with using MessageBox:es in the selectedItem setter.
I guess using some clever workaround is the appropriate solution in this case.
** Update March 2022
The URL referenced by the original post is no longer valid. The content can now be found here: WPF TabControl bug
I am using the DevComponents TabNavigation control for WPF, and am able to add a new TabItem to the TabNavigation at a specific index, call it i, in the code-behind. Now I want to make the new TabItem the SelectedItem, by doing:
private void textBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int i = createNewTabItem(0, "Foo");
TabNavigation tn = (((sender as TextBlock).Parent as Grid).Parent as TabItem).Parent as TabNavigation;
tn.SelectedItem = tn.Items[i];
}
private int createNewTabItem(int overflowSrcPageNum, String header)
{
TabItem ti = new TabItem();
ti.Header = header;
tabNavigation.Items.Insert(overflowSrcPageNum + 1, ti);
return overflowSrcPageNum + 1;
}
When I run this code, however, instead of the new TabItem being brought into view, it is brought into view and then the original tab I was on is quickly moved back into view.
If anyone has any ideas as to why this is happening, and how I can fix it please let me know. I have attached a sample of the XAML below:
<Grid >
<Grid.Resources>
<ResourceDictionary>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextDecorations" Value="Underline"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Foreground" Value="White" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="11" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Text" Value="View More..." />
<Setter Property="Visibility" Value="Visible" />
<EventSetter Event="MouseLeftButtonDown" Handler="lblMoreCpartys_MouseLeftButtonDown" />
</Style>
</ResourceDictionary>
</Grid.Resources>
<my:TabNavigation Background="Black" HorizontalAlignment="Stretch" Margin="0" Name="tabNavigation"
VerticalAlignment="Stretch" MouseLeftButtonDown="tabNavigation_MouseLeftButtonDown"
FontSize="12" Foreground="SteelBlue" ForceCursor="True" MouseWheel="tabNavigation_MouseWheel"
TabStripPlacement="Bottom">
<TabItem Header="ITEM 1" Name="firstTabItem" FontSize="12" >
<TextBlock Name="firstTB" />
</TabItem>
<TabItem Header="ITEM 2" Name="secondTabItem" FontSize="12" >
<TextBlock Name="secondTB" />
</TabItem>
</my:TabNavigation>
</grid>
Thanks in advance.
Try setting e.Handled to True in textBlock_MouseLeftButtonDown.
I'm not familiar with that control, but if it works like TabControl then it has logic to bring a tab into view when it is clicked. That logic sees that the original tab was clicked, and brings it back into view after your change. Marking the EventArgs object as Handled will stop WPF from calling event handlers on parent elements, which would stop the tab from switching back.