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

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.

Related

Expander.IsExpanded Binding breaking after first click

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...

XAML binding properties to UserControl in ContentPresenter

I have UserControl called EditorView that shows different "editors" (other user controls) based on its Content.
This is EditorView just to test the binding I replaced the FontEditor with TextBlock:
<UserControl x:Class="TrikeEditor.UserInterface.EditorView" ...>
<UserControl.Resources>
<DataTemplate DataType="{x:Type te_texture:Texture}">
<teuied:TextureEditor TextureName="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type te_font:Font}">
<!--<teuied:FontEditor/>-->
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</UserControl.Resources>
<UserControl.Template>
<ControlTemplate TargetType="{x:Type UserControl}">
<ContentPresenter Content="{TemplateBinding Content}" x:Name="EditorPresenter"/>
</ControlTemplate>
</UserControl.Template>
</UserControl>
The right template is getting picked based on the EditorView.Content and in the case of TextBlock the binding works as desired but in the case of TextureEditor the TextureName property isn't.
Here is snippet from the TextureEditor:
public partial class TextureEditor : UserControl
{
public static readonly DependencyProperty TextureNameProperty = DependencyProperty.Register("TextureName", typeof(string), typeof(TextureEditor));
public string TextureName
{
get { return (string)GetValue(TextureNameProperty); }
set { SetValue(TextureNameProperty, value); }
}
public TextureEditor()
{
InitializeComponent();
}
}
Is there anything special that I have to do since I'm using UserControl? Maybe being different namespace is the problem?
The User Control shouldn't affect it; the difference is that you're implementing your own dependency property (rather than using the existing one Text in TextBlock). You have to set the value of the TextureName property in the Dependency Property PropertyChanged handler:
public static readonly DependencyProperty TextureNameProperty =
DependencyProperty.Register("TextureName", typeof(string), typeof(TextureEditor),
// on property changed delegate: (DependencyObject, DependencyPropertyChangedEventArgs)
new PropertyMetadata((obj, args) => {
// update the target property using the new value
(obj as TextureEditor).TextureName = args.NewValue as string;
})
);

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>

WPF User control binding issue

This should be a very simple case, but I am pulling hair trying to get it to work. Here is the setup:
I am designing an app that will have an read-only mode and edit mode for some data. So I created a User Control which is a textbox and textblock bound to the same text data and are conditionally visible based on EditableMode property (so when it's editable the textbox is shown and when it's not the textblock is shown)
Now, I want to have many of these controls in my main window and have them all bound too a single bool property. When that property is changed via a button, I want all TextBlocks to turn into TextBoxes or back.
My problem is that the control is set correctly on binding, and if I do myUserControl.Editable = true. But it doesn't change if bind it to a bool property.
Here is the code for my user control:
<UserControl x:Class="CustomerCareTool.Controls.EditableLabelControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:CustomerCareTool.Converters"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<src:BoolToVisibility x:Key="boolToVisibility" Inverted="False" />
<src:BoolToVisibility x:Key="invertedBoolToVisibility" Inverted="True" />
</UserControl.Resources>
<Grid>
<TextBlock Name="textBlock" Text="{Binding Path=TextBoxValue}" Visibility="{Binding Path=EditableMode, Converter={StaticResource invertedBoolToVisibility}}"/>
<TextBox Name="textBox" Visibility="{Binding Path=EditableMode, Converter={StaticResource boolToVisibility}}">
<TextBox.Text>
<Binding Path="TextBoxValue" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
</Grid>
I used a converter to convert bool to visibility and inverse bool to visibility. Not sure if that's at all needed here.
And this is the code behind:
public partial class EditableLabelControl : UserControl
{
public EditableLabelControl()
{
InitializeComponent();
}
public string TextBoxValue
{
get { return (string)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue", typeof(string), typeof(EditableLabelControl), new UIPropertyMetadata());
public bool EditableMode
{
get { return (bool)GetValue(EditableModeProperty); }
set { SetValue(EditableModeProperty, value); }
}
public static readonly DependencyProperty EditableModeProperty =
DependencyProperty.Register("EditableMode", typeof(bool),typeof(EditableLabelControl), new UIPropertyMetadata(false, EditableModePropertyCallBack));
static void EditableModePropertyCallBack(DependencyObject property,
DependencyPropertyChangedEventArgs args)
{
var editableLabelControl = (EditableLabelControl)property;
var editMode = (bool)args.NewValue;
if (editMode)
{
editableLabelControl.textBox.Visibility = Visibility.Visible;
editableLabelControl.textBlock.Visibility = Visibility.Collapsed;
}
else
{
editableLabelControl.textBox.Visibility = Visibility.Collapsed;
editableLabelControl.textBlock.Visibility = Visibility.Visible;
}
}
}
Now in my main application I have the control added like this:
<Controls:EditableLabelControl x:Name="testCtrl" EditableMode="{Binding Path=Editable}" TextBoxValue="John Smith" Grid.Row="0"/>
For that same application the DataContext is set to self
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And the code behind looks like this:
public partial class OrderInfoView : Window, INotifyPropertyChanged
{
public OrderInfoView()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Editable = !Editable;
}
private bool _editable = false;
public bool Editable
{
get
{
return _editable;
}
set
{
_editable = value;
OnPropertyChanged("Editable");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Clicking the button doesn't do anything :( I tried everything to get this to work, and no dice. Would really appreciate some help!
I tried the following, and still does not work:
public bool Editable
{
get { return (bool)GetValue(EditableProperty); }
set { SetValue(EditableProperty, value); }
}
public static readonly DependencyProperty EditableProperty =
DependencyProperty.Register("Editable", typeof(bool), typeof(OrderInfoView), new UIPropertyMetadata(false));
It looks like your solution may be more complex than necessary. If all you want to do is have a disabled TextBox look like a TextBlock then you can do this using a trigger and a template. Then you can apply that style to all text boxes.
Here's an example of that approach:
<Window x:Class="WpfApplication25.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<Window.Resources>
<!-- Disable TextBox Style -->
<Style x:Key="_DisableTextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<!--
Be sure to apply all necessary TemplateBindings between
the TextBox and TextBlock template.
-->
<TextBlock Text="{TemplateBinding Text}"
FontFamily="{TemplateBinding FontFamily}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox IsEnabled="{Binding IsChecked, ElementName=uiIsEnabled}"
Style="{StaticResource _DisableTextBoxStyle}"
/>
<ToggleButton x:Name="uiIsEnabled" Content="Enable" IsChecked="True" />
</StackPanel>
</Window>
INotifyPropertyChanged does not work for classes that derive from DependencyObject.
Editable property in OrderInfoView must be dependency property in order for binding to work correctly, although technically your code is correct but I feel its bug in WPF that when object is dependency object it ignores INotifyPropertyChanged event because it is searching for notification in property system.
<Controls:EditableLabelControl x:Name="testCtrl"
EditableMode="{Binding Path=Editable,ElementName=userControl}" TextBoxValue="John Smith" Grid.Row="0"/>
Specify ElementName in binding tag and also name your usercontrol with x:FieldName or x:Name
I just came across this searching for something else.
Without reading your post in detail (no time atm sorry) it seems to me you're having a similar issue to the one I posted about here:
http://jonsblogat.blogspot.com/2009/11/wpf-windowdatacontext-and.html
In short, move your binding for your main window to the Grid and use a relative binding to see if that fixes your problem.

Resources