wpf combobox trigger for Validation.HasError - wpf

I've looked around, but can't specifically find my issue. I know that the default "Error" handling within WPF puts an "Adorner" around controls in case there are any errors based on IDataErrorInfo or Validataion rules failing a given control. That's all good and fine, however, with a tabbed page interface, if any controls are so flagged as invalid, they are properly adorned in red border. However, as soon as you go from tab page 1 to 2 and back to 1, all the adorners are gone (bad). This was already asked, and solution accepted, but was looking for a better alternative.
So, I went to my "Themes" declaration, and for the textbox control, I just said to set the entire background color of the control to red and not just the border. Without any fancy forced triggering via Notify on Property Changed, if I swap between pages, the red background of the entire textbox remains constant.
Now, on to the combobox control. For those who have customized their own, or even looked into the default MS version of the control, its actually a clustered mess of controls, grids, columns, buttons, etc to make the magic of combobox work. In brief...
ControlTemplate
Grid (two columns, one for text display of chosen, second column for the drop-down arrow)
Border spanning both columns
Path ( line drawing / glyph for the drop-down image for combobox )
ControlTemplate TargetType Textbox (as part of the entire combobox set)
Border specifically "PART_ContentHost"
ControlTemplate of combobox
Grid
Toggle Button
Dropdown exposed showing list
other triggers..
Finally, the main ComboBox declaration which is templated by above components.
Anyhow, I can't for the life of me get this. In the "Toggle Button" area of the combobox declaration, I have a trigger to change the background to an OBVIOUS off color for proof of testing the trigger working and in the right location within the ControlTemplate declarations.
So, knowing this is the correct place within the combobox declarations, I want to supersede the green background color with red if there's an error with the data. I KNOW the overall "Validation.HasError" is properly getting triggered as the native error handler shows. No matter how / where within the template I try to change the background color to red, it does NOT work. I've even tried doing DataTriggers, using converters, trying multiple properties, but it appears not to be cooperating.
Any suggestions? This is getting really annoying.

FINALLY, got it... and not as obvious as I would have guessed. Anyhow, here's what I've found. If you went with a sample from Microsoft's template of the combobox, they first provide the overall two-column "ToggleButton" declaration
<ControlTemplate TargetType="ToggleButton"
x:Key="baseComboBoxToggleButton" >
... blah blah...
</ControlTemplate>
Then, the declaration for the "Display Value" of the combobox
<ControlTemplate TargetType="TextBox" x:Key="ComboBoxTextBox" >
<Border x:Name="PART_ContentHost" Focusable="False"
Background="{TemplateBinding Background}" />
</ControlTemplate>
Then, tie them together as one Combobox "wrapper" declaration
<ControlTemplate TargetType="ComboBox" x:Key="ComboBoxGridControlTemplate" >
<Grid x:Name="GridComboWrapper">
<!-- This is the dropdown button that POINTS TO THE "baseComboBoxToggleButton at the top -->
<ToggleButton Name="ToggleButton"
Template="{StaticResource baseComboBoxToggleButton}"
Grid.Column="2" Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press" >
</ToggleButton>
...
rest of the content presenter,
EDIT(able) textbox area,
popup area of combobox when in drop-down mode
</Grid>
<ControlTemplate.Triggers>
<!-- PUT THE VALIDATION CHECK HERE -->
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<!-- THIS IS THE CRITICAL COMPONENT... I HAD TO EXPLICITLY TELL
The TagetName as the "ToggleButton" and change ITs Background property
and it now works -->
<Setter TargetName="ToggleButton" Property="Background"
Value="{StaticResource BrushDataInvalidBorder}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
So, now it works as intended and doesn't loose any adorner just because the active page on a given form changes and clears it... its static to each individual control as expected... Wow... what a PITA this one was.
Hope it helps someone ELSE in the future from excessive head banging against a wall while learning this nested level of stuff.

Related

WPF DataGrid top-left button set/view Content?

Trying to set the Content property of a WPF DataGrid top-left button at run time. I get the button object using the VisualTreeHelper of the DataGrid object and then I successfully set its Content property, as verified using Snoop while running the application. However, the button text is not visible. I suspect this is because there are UI elements on top of the button that use non-transparent background brushes. Upon reading the docs I see a grid that uses storyboards and a rectangle that uses gradient brushes.
Other than editing the WPF DataGrid top-left button style template, what are my options for making the button Content (text) visible?
You could entirely replace the template.
I'd prefer to bind the text of the textblock to a property in a viewmodel personally.
This could get you started.
Put this in scope of the datagrid like in your window resources or a resource dictionary merged in app.xaml.
<Style x:Key="{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="Red">
<TextBlock Text="X" Foreground="White"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Needs more work, but it shows up on a datagrid sample I have.
( This is something which has dozens of styles from experiments answering questions elsewhere in it ).
(Something funny happens, I am now posting under the same user name as before but SO insists that I must recreate my profile every time I log in. Go figure.)
Thank you Andy for the solution you proposed. For the technical reason above, I am unable to mark this question "Solved".
Now I think a quick way around this is to place a transparent label right on top of the DataGrid button, with IsHitTestVisible = false. I noticed a couple interesting things though:
It seems the button can have a Grid or a string for Content but not both;
The button is already created when the DataGrid Loaded event handler runs, e.g., a Click event handler can be added to it; however, setting the Content to a string at this time doesn't change anything. I guess Content changes when the DataGrid is populated.

Issue with WPF styles

I wonder if someone can help, I am designing a custom WPF window for an application i am working on and I have an issue with the Min, Max and Close buttons. I have designed a ControlTemplate for the 3 buttons and they are in a StackPanel with Vertical orientation. In my base style I have the following
<Style x:Key="BaseWindowButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value={Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}" />
<Setter Property="Background" Value="Transparent" />
...
</Style>
Have also tried setting the Foreground to a specific color such as #FF000000 and nothing displays
I have a style that inherits this style for the buttons but does not change the foreground or background.
My problem is that the button content does not display, the button displays and the IsMouseOver trigger fires which changes the background but the textual content never displays.
The Min button style is
<Button x:Name="PART_Min">
<Path Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Data="F1M0,6L0,9 9,9 9,6 0,6z"
SnapsToDevicePixels=True" />
</Button>
I am at a loss as to why the content does not display so would appreciate your thoughts.
The Visual Tree is below, I have examined this and identified the Foreground values
Window (Foreground: #FF000000)
Grid
AdornerDecorator
Grid
ContentControl (Foreground: #FF000000)
StackPanel
Button (Foreground: #FF000000)
Grid
ContentControl (Foreground: #FF000000)
But like I said above I have removed the binding and specified a physical value and still do not get the content displaying
Use ContentPresenter instead of ContentControl in your button's template. (You should include the button's control template in a question like this... it's potentially highly relevant.)
As I too was a beginner with WPF, and trying to understand things was a bit of a learning curve, I would like to offer a few previous links posted out here.
First a simple style for creating simple label and having all labels as a default take on this format without explicit style assignments.
Another sample showing creation of a custom button. This is where I went step by step to create a custom class and apply a style to it to show/hide image on a button.
Maybe the button link and style declaration will help you find your button coloring issues too.

Collapse ContentControl if Content is Collapsed

I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.

Rolling effect in ListBox

This is a follow-up to this question
Increase FontSize for hovered element in WPF ListBox
I have a ListBox where I want a "rolling" effect. From start all my items in the ListBox have a FontSize of 12, and when IsMouseOver="True" they get FontSize=18. Now I want the item below and above the IsMouseOvered item to get FontSize 16. I've got a feeling this is not so straight forward. Anyone got any idea?
This is my ListBox now
<ListBox Name="ListBox" ItemsSource="{Binding MyList}" DisplayMemberPath="Property1">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextBlock.FontSize" Value="18"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Thanks
This is going to be difficult to accomplish in straight XAML, because there's no property that you can examine that tells you if the mouse is currently over the next (or previous) element in the list.
The way I'd implement this: I'd create a wrapper class for the items in your list that exposes the properties IsMouseOver, FontSize, and Content. I'd implement a parent class that maintains some kind of indexable collection of these items (like an array or a list), and that registers for its items' PropertyChanged events so that it can know which item in the list the mouse is presently hovering over. The parent class would then be responsible for adjusting the FontSize on the child objects whenever the currently moused-over item changes. All the XAML does is bind to the FontSize on the child objects.
Note, by the way, that if you implement some kind of cool effect (like the one in the example Aaron linked to, which incidentally doesn't do what you've said you want) that, in resizing elements when IsMouseOver changes, moves them on the screen in such a way that the element that the mouse is over changes too, your users will hunt you down and kill you in your sleep.
It appears you are going for the fish eye effect. There are varying solutions out there for WPF which provide this behavior for you in the form of a reusable panel for instance. They generally focus on images however the concept is the same. This solution shows an example in pure XAML.
I wrote this user control that would mimic the dashboard of the Mac's (Fish eye effect).

WPF Style Trigger

I change the FontSize of Text in a Style trigger, this causes the Control containing the text to resize as well. How can I change the Fontsize without affecting the parent's size?
A nice trick to isolate an element from its parent layout wise is to place the element in a Canvas
In the markup below there are two copies of your element
The first is hidden and establishes the size of your control
The second is visible but wrapped in a Canvas so its layout size does not affect the parent.
<Parent>
<Grid>
<Element Visibility="Hidden"/>
<Canvas>
<Element />
</Canvas>
<Grid>
</Parent>
You can increase the Padding at the same time you decrease the FontSize - this will cause the calculated height of the Button to remain the same:
<StackPanel>
<Button Content="ABC">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="20"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="5"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Margin="0,20" Content="123" FontSize="20"/>
<Button Content="Do Re Mi" FontSize="20"/>
</StackPanel>
You can do the reverse and set a negative Padding if the FontSize is increasing, as well.
You could also use a binding from FontSize to Padding to accomplish the same thing in a general way, but if you're only dealing with a fixed set of FontSizes it would be easier to just hardcode it as above.
There is absolutely NO need for hard-coded widths, crazy measure overrides, tricky bindings, or anything of that sort.
The solution is actually incredibly simple. Instead of changing the font size in a style trigger, create a simple control template for your button with a RenderTransform applied to the content presenter element. Add a ScaleTransform to the RenderTransform. Inside a IsPressed trigger definition set the vertical and horizontal scales on the ScaleTransform to a smaller ratio, say 0.8.
Using a RenderTransform will keep the layout of the pressed button the same with, so it won't influence the position of the other elements. By contrast, using a LayoutTransform would have actually caused the button container to shrink and the parent container's ArrangeOverride method would cause the adjacent buttons to move to fill the extra space.
I'm really busy right now so I'll leave the actual implementation up to you! ;-)
http://msdn.microsoft.com/en-us/library/system.windows.media.scaletransform.aspx
I am creating a ControlTemplate for a ButtonControl so it looks like a label (flat text, no borders) with triggers for IsKeyboardFocused, IsPressed, IsDefaulted etc.
The IsPressed is defined to drop the FontSize (from default of 30) down to 28. To give a pressed animation effect.
One use of these Buttons is a horizontal StackPanel of Button, separated by vertical separators. When the IsPressed trigger is fired on a button and it is resized, the entire row of buttons gets re adjusted, which is not a pleasing visual effect.
My preference is for a template based solution, to avoid introducing new controls in order to provide overrides. The only problem with the hard coded size approach is internationalisation, other languages will increase the orginal size.
The solution I am going with is to set the minWidth in C# after the button's DesiredSize has been calculated. Note that Width is NaN even after the Button is rendered hence the use/existence of DesiredSize. Later I will try and XAMLize the C#.
What kind of control are you using? If this is a HeaderedControl like a GroupBox or TabItem then you need to specifically set the HeaderTemplate like this:
<DataTemplate x:Key="MyHeaderTemplate">
<TextBlock Text="{Binding}" Fontsize="14" FontWeight="Bold" />
</DataTemplate>
I can think of a couple of things you could try:
You can override the Measure Pass of the control - when a control is rendered in WPF it undergoes two passes. The first is a 'measure pass', where the control comes up with what sizes that it wants to be. The second is the 'arrange pass', where it actually lays out the control. WPF provides a method called MeasureOverride. If you override this method you can provide custom behavior that can be used to adjust the size of the control.
Note - I believe that you will have to call the Measure method all of your controls children during this override in order to get your control to lay out properly.
Hard code the height and width on the control - this will override the control's DesiredSize with your values. While generally a not the greatest of ideas, it will work.

Resources