Expander.IsExpanded Binding breaking after first click - wpf

am probably doing something but can't figure out my problem. Any help would be really appreciated:
I am having a CustomControl called Section. A Section is collapsible and therefore its ControlTemplate contains an Expander. The Expanders IsExpanded-Property is bound to the Section's IsExpanded Property via TemplateBinding.
When setting IsExpanded on a Section the Expander collapses, but using the toggleButton within the Expander appears to break that binding. Probably by setting a local value to the Expander's IsExpanded-Property. Anyways, after changing the Expander state via Mouse the binding breaks and setting the Section's IsExpanded doesn't do anything.
This however does not happen when putting an Expander into a view and binding its IsExpanded-Property to some DP in the View.
Also notworthy: Snoop does not show any Bindings on the Expander's IsExpanded-Property. It only shows the Value-Source is ParentTemplate. As soon as I click the ToggleButton to Change IsExpanded the Value-Source changes to Local (possibly breaking the former Binding?)
Section.cs:
public class Section : Control
{
static Section()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Section), new FrameworkPropertyMetadata(typeof(Section)));
}
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
"IsExpanded", typeof (bool), typeof (Section), new PropertyMetadata(default(bool)));
public bool IsExpanded
{
get { return (bool) GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
}
Generic.xaml Style:
<Style TargetType="{x:Type local:Section}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Section}">
<Expander Header="Test" IsExpanded="{TemplateBinding IsExpanded}" >
<Rectangle Fill="Aqua" Height="200" Width="200" />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Any ideas?

So I found the answer which is pretty basic knowledge actually:
TemplateBindings are always ONEWAY no matter what the MetaData states...
Using:
IsExpanded="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}"
fixed my problem...

Related

How to get the ActualWidth and ActualHeight dynamically of a customcontrol?

I have a custom control, ccTextBlock placed inside a ScrollViewer. The customcontrol will be changing sizes (vertically) when different strings are sent to it through the binding. The custom control will remain on the display, but will change as text elsewhere on the screen is selected.
How can I obtain the actual width and height of the custom control only after and with each text string sent to it? (Using OnApplyTemplate() did not work as it seems to be called only once on the first construction of the custom control.)
Thanks for any replies.
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<wc:ccTextBlock Text="{Binding Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</Grid>
</ScrollViewer>
Update: Perhaps a better way to phrase this question would be "How to get the height of an element when it is inside a ScrollViewer". Here is the definition of ccTextBlock:
public class ccTextBlock : Control
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ccTextBlock), new UIPropertyMetadata(null));
/// <summary>
/// Constructor
/// </summary>
static ccTextBlock()
{
// Initialize as lookless control
DefaultStyleKeyProperty.OverrideMetadata(typeof(ccTextBlock), new FrameworkPropertyMetadata(typeof(ccTextBlock)));
}
public override void OnApplyTemplate()
{
//Effectively apply the template
base.OnApplyTemplate();
Console.WriteLine(String.Format(" ActualHeight is {0}", this.ActualHeight.ToString()));
var x = this.FontSize;
}
}
Where Generic.xaml is:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary">
<Style TargetType="{x:Type local:ccTextBlock}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ccTextBlock}">
<!-- Control Layout -->
<TextBlock Text="{TemplateBinding Text}" TextWrapping="Wrap" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ActualWidth and ActualHeight are the properties that contain the current width and height of the control.
If you are looking for an Event that notifies about changes, it would be the FrameworkElement.SizeChanged event. You could register for this event in the OnApplyTemplate implementation.

Bind Canvas.Left and Canvas.Top in Style

I am trying to reflect a position from my view-model expressing the location of an object in my view by binding the Canvas.Left and Canvas.Top properties in the style of the item view to appropriate properties in the view-model. However, the bindings do not seem to work.
For this minimal sample, I have simplified the structure so there only is one control Thing that is styled and templated:
using System;
using System.Windows;
using System.Windows.Controls;
namespace LocationBinding
{
public class Thing : Control
{
static Thing()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Thing), new FrameworkPropertyMetadata(typeof(Thing)));
}
public Point Location {
get {
return new Point(70, 70);
}
}
public double VPos {
get {
return 100;
}
}
}
}
For the sake of simplicity, I have declared the style in the resource dictionary of the main window - a simple window with a canvas on it (my real project has this in Themes\Generic.xaml). In its style, I am binding to property values of the control:
<Window x:Class="LocationBinding.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LocationBinding"
Title="LocationBinding" Height="300" Width="300">
<Window.Resources>
<Style TargetType="local:Thing">
<Setter Property="Panel.ZIndex" Value="542"/>
<Setter Property="Canvas.Left" Value="{Binding Location.X}"/>
<Setter Property="Canvas.Top" Value="{Binding VPos}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Thing">
<Ellipse Fill="ForestGreen" Width="30" Height="30"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Canvas Name="cnv">
</Canvas>
</Window>
The code-behind of the main window simply adds a Thing instance to the canvas:
using System;
using System.Windows;
namespace LocationBinding
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
cnv.Children.Add(new Thing());
}
}
}
The style is correctly applied, as evidenced by the ZIndex value (according to Snoop) and the correct template-based appearance of the control. However, Canvas.Left and Canvas.Top remain unset (and thus the Thing sticks in the top left corner of the canvas), even though according to threads such as this or this, Property="Canvas.Left" seems to be the correct syntax to refer to the attached property in a style.
I was first trying to bind Canvas.Top to Location.Y and replaced that with the VPos property in case the problem is related to binding to struct properties, but that does not seem to change anything, either.
What am I missing; how can I bind Canvas.Left and Canvas.Top in my style to the coordinates from my Thing.Location property?
By default, binding will search for property in DataContext of control but Location is your control (Thing) property.
So you need to use RelativeSource with Mode set to Self to tell binding engine to search for property in control itself and not in DataContext of control:
<Setter Property="Canvas.Left" Value="{Binding Location.X,
RelativeSource={RelativeSource Self}}"/>

How to handle attached properties events?

I created an expander style that contains a checkbox in its header. The checkbox state is bound to an attached property:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
(...)
<CheckBox x:Name="ExpanderHeaderChk" VerticalAlignment="Center" Margin="4,0,0,2"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
(...)
I my view, inside the expander I have a stackpanel with a ComboBox.
Whenever the user checks the expander's checkbox, I wan't that the combobox gets the first item selected, on the oher hand whenever the user unchecks it, I wan't that the selecteditem of the combobox be null.
How can I accomplish this? I'm following the MVVM pattern, but since this is more a matter of the view, I'm open to code-behind suggestions.
Well, I think your design is not optimal. You see, you are trying to change the semantics of the Expander. The real expander doesn't have the semantics with additional checkbox, so the control you are creating is not an Expander any more.
I would suggest that you switch to a user control (or maybe a custom control, look at your semantics), and expose the needed event in your control's class. The XAML for the user control should be perhaps an expander with a checkbox.
Edit: example with UserControl (not tested)
(XAML)
<UserControl x:Class="namespace:MyCheckboxExpander">
<Expander>
...
<Checkbox x:Name="cb"/>
...
</Expander>
</UserControl>
(code-behind)
public class MyCheckboxExpander : UserControl
{
MyCheckboxExpander()
{
InitializeComponent();
cb.Check += OnCheck;
}
void OnCheck(object sender, whatever2 args)
{
if (CheckboxTriggered != null)
CheckboxTriggered(new EventArgs<whatever>);
}
public event EventArgs<whatever> CheckboxTriggered;
}
WPF is so powerfull framework, that you can solve you problem just using next style for Expander:
<Style x:Key="myExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<CheckBox x:Name="PART_CheckBox" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding Content}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="PART_ComboBox" Property="SelectedIndex" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SAMPLE:
<Expander Style="{StaticResource myExpanderStyle}">
<x:Array Type="sys:String">
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Expander>
Just XAML! I like XAML declarativity.
But from MVVM perspective, this approach has one disadvantage - I can't cover this case with unit tests. So, I would prefer:
create view model with properties: IsChecked(bound to CheckBox),
SelectedItem(bound to ComboBox) and Source(ItemsSource for ComboBox) -
abstration of my real view without any references on controls;
write a logic in view model that set or unset SelectedItem depending
on IsChecked property;
cover that logic with unit test (yep, you can
even start with this point, if you like test first approach).
I followed the suggestion provided by #Baboon and I created a custom control with a routed event named CheckedChanged, this way I can access it through the view's xaml and code-behind:
[TemplatePart(Name = "PART_Expander", Type = typeof(Expander))]
[TemplatePart(Name = "PART_CheckBox", Type = typeof(CheckBox))]
public class MyCustomExpander : Expander
{
static MyCustomExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomExpander), new FrameworkPropertyMetadata(typeof(MyCustomExpander)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(MyCustomExpander),
new UIPropertyMetadata(false));
#region Events
private CheckBox chkExpander = new CheckBox();
public CheckBox ChkExpander { get { return chkExpander; } private set { chkExpander = value; } }
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("ExtraButtonClick",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyCustomExpander));
public event RoutedEventHandler CheckedChanged
{
add { AddHandler(CheckedChangedEvent, value); }
remove { RemoveHandler(CheckedChangedEvent, value); }
}
void OnCheckedChanged(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CheckedChangedEvent, this));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox chk = base.GetTemplateChild("PART_CheckBox") as CheckBox;
if (chk != null)
{
chk.Checked += new RoutedEventHandler(OnCheckedChanged);
chk.Unchecked += new RoutedEventHandler(OnCheckedChanged);
}
}
#endregion
}
I want to thank to #Baboon and #Vlad for their help.

How to change font color of items in listbox (wpf)

I have a listbox which is bounded to an observable collection. The elements in the collection contain a variable called color. My listbox's items are already bounded to the collection, but how do I also bind the items font color to that? I already have a data template which works fine replacing item's name with the color name like this
<DataTemplate x:Key="myListBox">
<TextBlock Padding="0,0,10,0"
Text="{Binding Path=Color, Mode=Default}"/>
</DataTemplate>
but I can't seem to find which property I have to set in order to bind the color.
Not sure which colour you're referring to, but this will set the background and text/foreground colours.
<TextBlock Padding="0,0,10,0"
Text="{Binding Path=Color, Mode=Default}"
Background="{Binding myBackgroundColour}"
Foreground="{Binding myTextColour}"
/>
EDIT: dependancy prop -
public string Color
{
get { return (string)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
// Using a DependencyProperty as the backing store for Color. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("Color", typeof(string), typeof(CLASSNAMEHERE), new UIPropertyMetadata("Black"));
Replace CLASSNAMEHERE with the name to the class you're putting it in, ie the viewmodel class or codebehind class name.
use:
this.Color = "Yellow";
you can use this resource style
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Foreground" Value="></Setter>
<Setter Property="FontWeight" Value="Bold"></Setter>
bla bla bla
</Style>

Attached behavior binding to element in controltemplate

I am adding an attached behaviour to a slider which will cause it to scroll some content when the thumb is dragged and held over a specific region. (Can't use a straightforward IsMouseOver trigger as the Slider Thumb has MouseCapture.)
The behaviour has 3 properties:
#region IsScrollHoverProperty
public static readonly DependencyProperty IsScrollHoverProperty = DependencyProperty.RegisterAttached(
"IsScrollHover",
typeof(Boolean),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(false));
#endregion
#region ScrollLeftRectProperty
public static readonly DependencyProperty ScrollLeftRectProperty = DependencyProperty.RegisterAttached(
"ScrollLeftRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
#region ScrollRightRectProperty
public static readonly DependencyProperty ScrollRightRectProperty = DependencyProperty.RegisterAttached(
"ScrollRightRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
The IsScrollHoverProperty is being set to true when the user drags the slider, this is all done in the Slider's ControlTemplates.Triggers, and works correctly.
When it's set to true the callback is going to hook PreviewMouseEnterHandlers into the two Rectangles to detect when the mouse enters them.
The Rectangles in question are also defined in the Slider's controltemplate thusly:
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Left" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollLeftRect"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Right" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollRightRect"/>
</StackPanel>
The problem I have is binding these Rectangles to the attached ScrollRightRect and ScrollLeftRect Properties. I have tried a few things and suspect I have made a stupid binding error or am trying to do something not allowed. I am currently binding them in the controltemplate.triggers as follows:
<Trigger Property="local:ScrollHoverAreaBehaviour.IsScrollHover" Value="False">
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollLeftRect" Value="{Binding ElementName=ScrollLeftRect}"/>
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollRightRect" Value="{Binding ElementName=ScrollRightRect}"/>
<Setter TargetName="ScrollLeftRect" Property="Fill" Value="Red"/>
<Setter TargetName="ScrollRightRect" Property="Fill" Value="Red"/>
</Trigger>
I know this Trigger is being tripped as the rectangles fill Red as expected.
Can anyone spot what I'm doing wrong from these snippets?
Thanks in advance.
Rob
First, let's confirm you're not doing anything wrong, and the problem has nothing to do with the attached behaviors.
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="Yellow">
<StackPanel>
<TextBlock x:Name="theText" Text="Hello" />
<ContentPresenter />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Content" Value="{Binding ElementName=theText, Path=Text}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
This snippet should cause "Hello" to appear twice when I mouse over the button, but it doesn't, and I get the same error as you:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=theText'. BindingExpression:Path=Text; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type 'Object')
This is explainable - once the binding is set on the Button, it won't be able to find a control named 'theText', because the Button lives in a different NameScope.
An alternative
Some WPF controls need to do something similar to you - they assume that a specific control exists in the tree that they will interact with. But they don't use properties - they use names.
Start by giving the controls a name - the convention is to use "PART_" prefix:
<Rectangle ... Name="PART_ScrollLeftRect" />
Now put code like this in your callback when IsScrollHover is set:
private static void IsScrollHoverSetCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (Slider) d;
if ((bool)e.NewValue == false)
return;
target.ApplyTemplate();
var leftRectangle = target.Template.FindName("PART_ScrollLeftRect", target);
var rightRectangle = target.Template.FindName("PART_ScrollRightRect", target);
// Do things with the rectangles
}
Note that depending on when the IsScrollHost property is set, the template might not be ready yet. In that case, you might want to subscribe to the Loaded or similar event, and then call ApplyTemplate().
Although it might seem more complicated, it has one nice benefit: the markup will be simpler. A designer using Blend won't have to remember to wire up those complicated triggers, they just have to name the controls correctly.
The use of the PART_ prefix is a WPF convention, and normally used along with the TemplatePart attribute. An example of this is the TextBox. When you override the template of a TextBox, it won't function until you add a control named PART_ContentHost.
Update: I just blogged about template parts here: http://www.paulstovell.com/wpf-part-names

Resources