How does a ControlTemplate handle the datacontext?
Using the the follow Template
<ControlTemplate x:Key="ToolbarButtonHover" TargetType="Button">
<Grid Name="backgroundGrid">
<Image Source="{DynamicResource ResourceKey=Img}" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"></Image>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.ToolSelected, RelativeSource={RelativeSource TemplatedParent}}" Value="Unlink">
<Setter TargetName="backgroundGrid" Property="Background" Value="Red" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
with the control
<Button Content="Button"
Template="{StaticResource ResourceKey=ToolbarButtonHover}"
Height="24" Width="24" Background="Red">
<Button.Resources>
<ImageSource x:Key="Img">Resources/Icons/toolSelect.png</ImageSource>
</Button.Resources>
</Button>
But this does not make the background red. I have verifyed that the value of the ToolbarViewModel Property ToolSelected is in fact Unlink by having a <Label Content="{Binding ToolSelected}"/> next to the controll. So i believe the problem is that the template does not use the correct DataContext, but I'm not sure of this. That's why i ask you for help.
The Control lies in a custom usercontrol, and the ToolbarViewModel is set as context for all of it, like so.
<UserControl.DataContext>
<local:ToolboxView/>
</UserControl.DataContext>
Try removing RelativeSource from DataTrigger.Binding then it should work in current DataContext:
<DataTrigger Binding="{Binding ToolSelected}" Value="Unlink">
<Setter TargetName="backgroundGrid" Property="Background" Value="Red" />
</DataTrigger>
Related
I am new to wpf. Actully i want to change button icon(play.ico/stop.ico).
when i click the button it changes status(status is database column) value 'Online' to Offline, or Offline to Online.
It updating in database also but
I want to show when status is online buttton should show stop.ico and when I status is offline button should show play.ico.
How to achieve this? I tried with below code but its not working. please help and suggest what i am missing something.
//Xaml code
<StackPanel Margin="0 0 0 0" Grid.Column="5" Grid.Row="0" Grid.RowSpan="2">
<Button Name="Ignition_Button1" Click="Ignition_Button1_Click_1" Width="35" Height="35" Margin="16,5,16,0"
Style="{DynamicResource CircleButton}"
Command="{Binding StartStopCommand}">
<Button.ToolTip>
<ToolTip>
<StackPanel>
<TextBlock FontWeight="Bold"
Text="Start or Stop Control Center" />
</StackPanel>
</ToolTip>
</Button.ToolTip>
<StackPanel Orientation="Vertical">
<Rectangle Width="15" Height="15" StrokeThickness="0">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="{StaticResource play.ico}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status, UpdateSourceTrigger=PropertyChanged}" Value="Online">
<Setter Property="Fill" Value="{StaticResource stop.ico }" />
</DataTrigger>
<DataTrigger Binding="{Binding Status, UpdateSourceTrigger=PropertyChanged}" Value="Offline">
<Setter Property="Fill" Value="{StaticResource play.ico}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</StackPanel>
</Button>
</StackPanel>
Several things I would suggest here.
Rather than ico which are bitmap and will have jaggies, I would recommend geometries and paths.
With a button which has two states you toggle between then a togglebutton is a good candidate.
Below I put my two geometries in resources of my togglebutton.
These are more usually put into a resource dictionary (along with the many other geometries used for iconography) and merged in app.xaml.
<ToggleButton IsChecked="True"
IsThreeState="False"
>
<ToggleButton.Resources>
<Geometry x:Key="StopGeom">
M0,0L32,0 32,32 0,32z
</Geometry>
<Geometry x:Key="PlayGeom">
M0,0L16,8 32,16 16,24 0,32 0,16z
</Geometry>
</ToggleButton.Resources>
<Path Fill="Black"
Stretch="Uniform"
Margin="4">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Data" Value="{StaticResource PlayGeom}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType={x:Type ToggleButton}}}" Value="True">
<Setter Property="Data" Value="{StaticResource StopGeom}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</ToggleButton>
This needs more work but should give you the idea of one way to do this.
For more about layout and geometries:
https://social.technet.microsoft.com/wiki/contents/articles/32610.wpf-layout-lab.aspx
I don't know how you're styling the shape of your button, but I use this one to emphasise to wpf newbies what lookless controls means:
https://social.technet.microsoft.com/wiki/contents/articles/29866.aspx
I am trying to create a sort of left menu for navigation inside the desktopapplication. My idea is to use Buttons as Listview items which should behave in this way: when i hover with the mouseover them theri background should change color (becomes darkCyan) and when i click one its background color should change persistently (to darkCyan) until i click on another button of the list. The problem is that i am using a the DataTemplate property to specify how the buttons should look like and I am tryin to apply the triggers to change the background color on the ControlTemplate of the ListView. The result is that sometimes the background color changes but the command related to the button is not fired other times the contrary. I think that I am doing the things in the wrong element of the tree view, but I don't have enough knowledge of the tree view so I am not understanding what I am doing wrong. Here is the code of the XAML in which i define the styles for the Buttons and the ListView
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:TripViewModel}">
<views:TripView />
</DataTemplate>
<Style TargetType="{x:Type Button}">
<Setter Property="Height"
Value="50" />
<Setter Property="Background"
Value="#555D6F" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border>
<Grid Background="Transparent">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="Transparent"
BorderThickness="0"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Background"
Value="DarkCyan" />
</Trigger>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="Background"
Value="DarkCyan" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And here is the code in which I create the ListView
<ListView Name="MenuButtons"
ItemsSource="{Binding PageViewModels}"
Background="Transparent"
IsSynchronizedWithCurrentItem="True">
<ListView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Margin="0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Anyone can help?
Thanks
I have solved the issue by using a ListBox instead of a ListView and setting the ItemContainer to be a button in the following way
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
Margin="0" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>
I know that a passwordbox in wpf could not use the Validation.ErrorTemplate, anyhow i have to show the user, that something is wrong.
My Passwordbox has a binding like this
<PasswordBox Name="Password" local:PasswordHelper.Text="{Binding PasswordProp, Mode=TwoWay}" />
Is it possible to get the same style like the default errortemplate (red border) for this passwordbox, if something is wrong?
This is my ErrorTemplate that I use for the other controls
<Style x:Key="baseControlStyle">
<Setter Property="Control.FontFamily" Value="Verdana" />
<Setter Property="Control.FontSize" Value="12" />
<Setter Property="ToolTipService.ShowOnDisabled" Value="True" />
<Setter Property="Validation.ErrorTemplate" >
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Image x:Name="Bild"
DockPanel.Dock="Right"
Source="../Resources/Nein.ico"
Margin="-5 0 0 0"
MaxHeight="16"
MaxWidth="16"
VerticalAlignment="Center"
ToolTip="{Binding ElementName=myControl, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</Image>
<Border BorderBrush="Red" BorderThickness="1" CornerRadius="2">
<AdornedElementPlaceholder x:Name="myControl" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="Control.ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Thanks
One solution would be to put an actual TextBox underneath the PasswordBox and bind the Text property to PasswordProp too and give the TextBox the ErrorTemplate:
<Grid>
<TextBox Template="{x:Null}" Style="{StaticResource baseControlStyle}" Text="{Binding PasswordProp, Mode=TwoWay}" />
<PasswordBox Name="Password" local:PasswordHelper.Text="{Binding PasswordProp, Mode=TwoWay}" />
</Grid>
Since the controls of the ErrorTemplate will be put on an adorner layer, your error template will be displayed on top of the PasswordBox event though the TextBox is underneath the PasswordBox.
Also note that I have set the TextBox controltemplate to null. Since it is not supposed to be visible it doesn't need to be rendered.
Why is there no tooltip text on errors?
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Border ...>
<AdornedElementPlaceholder ...
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Border>
...
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I also noticed that
<AdornedElementPlaceholder ...
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
fails but the below suceeds, even with the same binding, why is this so? Doesn't AdornedElementPlaceholder refer to the text box? Even if it doesn't, shouldn't a tooltip appear somewhere?
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
I know I'm late, but let me share a solution I found studying this question: WPF custom validator with tooltip.
In it's simplest form this ErrorTemplate shows only a Tooltip with the ErrorContent for the whole AdornedElement.
<ControlTemplate x:Key="validationTemplate">
<Grid Background="Transparent"
ToolTip="{Binding Path=/ErrorContent}">
<AdornedElementPlaceholder />
</Grid>
</ControlTemplate>
But of course you can decorate it as desired e.g. with a Tooltip for just a marker.
<ControlTemplate x:Key="validationTemplate">
<Grid>
<Ellipse Fill="Red" Opacity="0.8" Width="10" Height="10"
HorizontalAlignment="Right" VerticalAlignment="Top"
ToolTip="{Binding Path=/ErrorContent}" />
<AdornedElementPlaceholder />
</Grid>
</ControlTemplate>
Put this Template in Resources and all you have to do is setting the Validation.ErrorTemplate.
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Even this annoying Trigger is no longer needed.
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
You can't place a tooltip on the AdornedElementPlaceholder, I don't think it's visible at all, it's just reserving space for whoever uses it (in your case a TextBox). Looking at the Visual Tree with Snoop we can see that the TemplatedAdorner ends up in a different place in the VisualTree than the TextBox so there will be now way for us to find the TextBox from the VisualTree. We can find it through AdornedElement, but we still won't be able to set a tooltip.
The only thing visible here in the TemplatedAdorner is the Border. The Border knows its Child - the TemplatedAdorner - which in turn knows its AdornedElement - the TextBox. So we could set the ToolTip for the Border with this. (However, this Binding seems to fail to update the Tooltip for the Border. It works when I look at it with Snoop and after that it displays.)
<Border BorderBrush="Red"
BorderThickness="4"
ToolTip="{Binding RelativeSource={RelativeSource self},
Path=Child.AdornedElement.(Validation.Errors)[0].ErrorContent}">
So, the TextBox has its AttachedProperty Validation where we can find the ErrorContent so it must set its own ToolTip like you did at your last example, otherwise it won't work.
I found a way to implement ToolTip with the returned error message from the validation class that you might create to validate your input.
First: Binding the error message
Adding <Style> for the TextBox with Style.Trigger as followed:
<Style TargetType="{x:Type TextBox}" x:Key="ToolTipError">
<!-- Some style setters -->
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Second: Add the style to TextBox
<TextBox
Style="{StaticResource ToolTipError}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<TextBox.Text>
<Binding
Path="YourViewModelProperty"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnNotifyDataErrors="True"
ValidatesOnDataErrors="True"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<ExceptionValidationRule:DateValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Bonus!
You can change the ugly red border with other thing.
For example you can change it to red exclamation mark:
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<StackPanel>
<TextBlock Text="!" FontSize="26" Foreground="Red"/>
<AdornedElementPlaceholder/>
</StackPanel>
</ControlTemplate>
<Window.Resources>
I have a listbox containing and image and a button. By default the button is hidden. I want to make the button visible whenever I hover over an item in the listbox.
The XAML I am using is below. Thanks
<Window.Resources>
<Style TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}" Height="150" Width="150"/>
<Button x:Name="sideButton" Width="20" Visibility="Hidden"/>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Ok, try this in your button declaration:
<Button x:Name="sideButton" Width="20">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsMouseOver}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
So I'm using a style with a trigger to look back up the visual tree until I find a ListBoxItem, and when its IsMouseOver property flips over to True I set the button's visibility to Visible.
See if it's close to what you want.
This Style does what you need. On mouse over, the button becomes only visible when the pointer is over the ListBoxItem. The special trick is to bind to the TemplatedParent for reaching IsMouseOver and use TargetName on the Setter to only affect the Button.
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}"
Height="150"
Width="150" />
<Button x:Name="sideButton"
Width="20"
Visibility="Hidden" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource TemplatedParent}}"
Value="True">
<Setter Property="Visibility"
TargetName="sideButton"
Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
#David is showing the right way,
But I have one suggestion to your XAML architecture. If you don't have any DataBinding on the Button it is better to put that in to the ListBoxItem style than the DataTemplate as bellow.
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="6">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=FullPath}"
Height="150"
Width="150" />
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Background="Transparent">
<Button x:Name="sideButton" Width="20" HorizontalAlignment="Right" Visibility="Hidden" />
<ContentPresenter/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Visibility"
TargetName="sideButton"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
One solution to find what item was clicked is to add the following Event setter
XAML
C#
void ListBoxItem_MouseEnter(object sender, MouseEventArgs e)
{
_memberVar = (sender as ListBoxItem).Content;
}
Just wondering, if we use the technique above, how do we determine what item the button was clicked on?
To answer Brian's question, in the button click handler you can walk up the visual tree to find the item that contains the button:
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListBoxItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep != null)
{
// TODO: do stuff with the item here.
}