WPF textbox that only updates binding when enter is pressed - wpf

all. I have a usercontrol "NumericTextBox" that only allows numeric entries. I need to exhibit another specialized behaviour, that is, I need it to be able to bind it to a VM value OneWayToSource and only have the VM value update when I press enter while focusing the textbox. I already have the an EnterPressed event that fires when I press the key, I'm just having a hard time figuring out a way to cause that action to update the binding...

In your binding expression, set the UpdateSourceTrigger to Explicit.
Text="{Binding ..., UpdateSourceTrigger=Explicit}"
Then, when handling the EnterPressed event, call UpdateSource on the binding expression, this will push the value from the textbox to the actual bound property.
BindingExpression exp = textBox.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();

Here is a complete version of the idea provided by Anderson Imes:
public static readonly DependencyProperty UpdateSourceOnKeyProperty =
DependencyProperty.RegisterAttached("UpdateSourceOnKey",
typeof(Key), typeof(TextBox), new FrameworkPropertyMetadata(Key.None));
public static void SetUpdateSourceOnKey(UIElement element, Key value) {
element.PreviewKeyUp += TextBoxKeyUp;
element.SetValue(UpdateSourceOnKeyProperty, value);
}
static void TextBoxKeyUp(object sender, KeyEventArgs e) {
var textBox = sender as TextBox;
if (textBox == null) return;
var propertyValue = (Key)textBox.GetValue(UpdateSourceOnKeyProperty);
if (e.Key != propertyValue) return;
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null) bindingExpression.UpdateSource();
}
public static Key GetUpdateSourceOnKey(UIElement element) {
return (Key)element.GetValue(UpdateSourceOnKeyProperty);
}

If you are using MVVM you can use a combination of decastelijau's approach along with a custom attached property that calls UpdateSource on the textbox when PreviewKeyUp.
public static readonly DependencyProperty UpdateSourceOnKey = DependencyProperty.RegisterAttached(
"UpdateSourceOnKey",
typeof(Key),
typeof(TextBox),
new FrameworkPropertyMetadata(false)
);
public static void SetUpdateSourceOnKey(UIElement element, Key value)
{
//TODO: wire up specified key down event handler here
element.SetValue(UpdateSourceOnKey, value);
}
public static Boolean GetUpdateSourceOnKey(UIElement element)
{
return (Key)element.GetValue(UpdateSourceOnKey);
}
Then you can do:
<TextBox myprops:UpdaterProps.UpdateSourceOnKey="Enter" ... />

Related

Accept Button and Data Binding inconsistency

It is convenient to have an "Accept Button" (in WPF: IsDefault="True") on a Form.
In the Windows Forms world, I used to read the data from the UI to the object(s) in the corresponding Click event of the button.
But with WPF, data binding ought to be used. In the constructor of the Window, I set this.DataContext = test;
And here comes the problem: the user entered some text in TextBox2, and hits the Enter key. Now, the command bound to the OK button gets executed, the data are saved.
But it is not the correct data! Why? TextBox2 has not yet lost focus, and consequently the ViewModel has not yet been updated.
Changing the UpdateSourceTrigger to PropertyChanged is not always appropriate (e.g. formatted numbers), I am looking for a general solution.
How do you overcome such a problem?
Typically I use a custom Attached Property to tell WPF to update the binding source when the Enter key is pressed
It is used in the XAML like this:
<TextBox Text="{Binding SomeProperty}"
local:TextBoxProperties.EnterUpdatesTextSource="True" />
And the code for the attached property is below:
public class TextBoxProperties
{
// When set to True, Enter Key will update Source
public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof(bool),
typeof(TextBoxProperties),
new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
// Get
public static bool GetEnterUpdatesTextSource(DependencyObject obj)
{
return (bool)obj.GetValue(EnterUpdatesTextSourceProperty);
}
// Set
public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
{
obj.SetValue(EnterUpdatesTextSourceProperty, value);
}
// Changed Event - Attach PreviewKeyDown handler
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (obj != null)
{
if ((bool)e.NewValue)
{
sender.PreviewKeyDown += OnPreviewKeyDownUpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
var obj = sender as UIElement;
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
}

WPF Datagrid: MVVM friendly way to bind SelectedCells to my ViewModel

I'm using the WPF datagrid, and have SelectionUnit="Cell" and SelectionMode="Extended". I'm also trying to adhere to the MVVM principals as much as I can.
I need my ViewModel to keep track of the current SelectedCells.
Life would be easy if I could just Bind its SelectedCells property to my ViewModel. Oddly enough, SelectedCells is only raised once - when we first select any cell in the grid.
MS explains it here: http://social.msdn.microsoft.com/Forums/en/wpf/thread/737117f4-6d20-4232-88cf-e52cc44d4431
Can anyone think of an MVVM-friendly approach to get around it?
Thanks!
I realized my last answer was for SelectedItems instead of SelectedCells, so I wrote a complete attached property class to do data binding for multiple SelectedCells which works as follows:
<controls:DataGrid ItemsSource="{StaticResource list}"
SelectionMode="Extended"
behaviors:DataGridSelectedCellsBehavior.SelectedCells="{Binding Path=SelectedGridCellCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I have a working source code and a demo project of it here.
Attached property behavior Code :
public class DataGridSelectedCellsBehavior
{
// Source : https://archive.codeplex.com/?p=datagridthemesfromsl
// Credit to : T. Webster, https://stackoverflow.com/users/266457/t-webster
public static IList<DataGridCellInfo> GetSelectedCells(DependencyObject obj)
{
return (IList<DataGridCellInfo>)obj.GetValue(SelectedCellsProperty);
}
public static void SetSelectedCells(DependencyObject obj, IList<DataGridCellInfo> value)
{
obj.SetValue(SelectedCellsProperty, value);
}
public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.RegisterAttached("SelectedCells", typeof(IList<DataGridCellInfo>), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null, OnSelectedCellsChanged));
static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty = DependencyProperty.RegisterAttached("SelectedCellsChangedEventHandler", typeof(SelectedCellsChangedEventHandler), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is DataGrid)//DataGrid
{
DataGrid datagrid = d as DataGrid;
SelectedCellsChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedCells(d) as IList<DataGridCellInfo>)
datagrid.SelectedCells.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedCells(d, datagrid.SelectedCells);
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
//else if (d is ListBox)
//{
// ListBox listbox = d as ListBox;
// SelectionChangedEventHandler selectionchanged = null;
// selectionchanged = (sender, e) =>
// {
// SetSelectedCells(d, listbox.SelectedCells);
// };
// SetSelectionChangedHandler(d, selectionchanged);
// listbox.SelectionChanged += GetSelectionChangedHandler(d);
//}
}
}
View Model Code :
class DemoViewModel : INotifyPropertyChanged
{
private IList<DataGridCellInfo> selectedGridCellCollection = new List<DataGridCellInfo>();
public IList<DataGridCellInfo> SelectedGridCellCollection
{
get { return selectedGridCellCollection; }
set
{
selectedGridCellCollection = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Do you need the SelectedCells constantly data-binded, or just when the user hits the OK/Accept button? If you only need it at the end of whatever process the user is in you can bind the SelectedCells to the CommandParameter property of a Button, for example. The SelectedCells is an IList, and you know enough to just do a cast to whatever object type the selection actually is. The other option is messier, you can use an attached property, keeping the event-handling out of your Views. This attached property would handle either a ListBox or in your case a DataGrid (MultiSelector).
public class Attach
{
public static IList GetSelectedItems(DependencyObject obj)
{
return (IList)obj.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject obj, IList value)
{
obj.SetValue(SelectedItemsProperty, value);
}
/// <summary>
/// Attach this property to expose the read-only SelectedItems property of a MultiSelector for data binding.
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(Attach), new UIPropertyMetadata(new List<object>() as IList, OnSelectedItemsChanged));
static SelectionChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectionChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectionChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty =
DependencyProperty.RegisterAttached("SelectionChangedHandler", typeof(SelectionChangedEventHandler), typeof(Attach), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is MultiSelector)//DataGrid
{
MultiSelector multiselector = d as MultiSelector;
SelectionChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedItems(d) as IList)
multiselector.SelectedItems.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, multiselector.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
multiselector.SelectionChanged += GetSelectionChangedHandler(d);
}
else if (d is ListBox)
{
ListBox listbox = d as ListBox;
SelectionChangedEventHandler selectionchanged = null;
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, listbox.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
listbox.SelectionChanged += GetSelectionChangedHandler(d);
}}}
Usage in XAML:
<DataGrid ItemsSource="{Binding Path=SourceList}"
myControls:Attach.SelectedItems="{Binding Path=myMvvmSelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended" />
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to synchronize the DataGrid.SelectedItems with the ViewModel. This might be very similar to SelectedCells.
Somewhere between perfect MVVM bindings and full event handler codebehinds there is the gray area of interactivity EventTriggers (see Blend SDK) :)
If you put an eventtrigger to the datagrid, and set to "SelectionChanged" and pass the eventargs to a command (use an EventToCommand actiontrigger) you could get the selected items from the eventargs hopefully...
Or use the multibinding as said in the MS thread :)

Why doesn't this Silverlight 4 DependencyObject's DependencyProperty getting data bound?

I have no idea why data binding is not happening for certain objects in my Silverlight 4 application. Here's approximately what my XAML looks like:
<sdk:DataGrid>
<u:Command.ShortcutKeys>
<u:ShortcutKeyCollection>
<u:ShortcutKey Key="Delete" Command="{Binding Path=MyViewModelProperty}"/>
</u:ShortcutKeyCollection>
</u:Command.ShortcutKeys>
</sdk:DataGrid>
The data context is set just fine since other data bindings that I have set on the grid are working just fine. The Command.ShortcutKeys is an attached DependencyProperty that is declared as follows:
public static readonly DependencyProperty ShortcutKeysProperty = DependencyProperty.RegisterAttached(
"ShortcutKeys", typeof(ShortcutKeyCollection),
typeof(Command), new PropertyMetadata(onShortcutKeysChanged));
private static void onShortcutKeysChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is UIElement && shortcuts != null)
{
var element = obj as UIElement;
shortcuts.ForEach(
sk => element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
public static ShortcutKeyCollection GetShortcutKeys(
DependencyObject obj)
{
return (ShortcutKeyCollection)obj.GetValue(ShortcutKeysProperty);
}
public static void SetShortcutKeys(
DependencyObject obj, ShortcutKeyCollection keys)
{
obj.SetValue(ShortcutKeysProperty, keys);
}
I know this attached property is working just fine since the event handlers are firing. However, the Command property of the ShortcutKey objects are not getting data bound. Here's the definition of ShortcutKey:
public class ShortcutKey : DependencyObject
{
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key", typeof(Key), typeof(ShortcutKey), null);
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
"Command", typeof(ICommand), typeof(ShortcutKey), null);
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
public class ShortcutKeyCollection : ObservableCollection<ShortcutKey> { }
The property that is getting bound to has its value set in the constructor of my view model, and its type is ICommand. So why isn't my Command property getting data bound? Also, have you found an effective way to debug data binding issues in Silverlight?
Edit:
At least one thing that was wrong was that ShortcutKey derived from DependencyObject instead of FrameworkElement, which is apparently the only root class that binding can be applied to. However, even after that change, the binding continued to not work properly.
You need to specify the Source of the Binding, since the DataContext is not inherited by members of the ObservableCollection.
edit:
Try setting the ShortcutKey.DataContext in onShortcutKeysChanged:
private static void onShortcutKeysChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var shortcuts = args.NewValue as ShortcutKeyCollection;
if (obj is FrameworkElement && shortcuts != null)
{
var element = obj as FrameworkElement;
ForEach(ShortcutKey sk in shortcuts)
{
sk.DataContext = element.DataContext;
element.KeyUp += (s, e) => sk.Command.Execute(null));
}
}
}
It looks like unless an object is inserted into the visual tree, no DataContext inheritance takes place, and thus no data binding works. I couldn't find a way to get the container's data context to be passed to the ShortcutKey objects, so as a workaround, I set up the binding in the code behind.
Hopefully someone else has a different answer that will show me how I won't have to resort to setting up this data binding in the code.

WPF Data Binding with StringFormat when UpdateSourceTrigger is PropertyChanged

I want to have a textbox append a specific string once the user tabs out of the control, i.e. LostFocus, however I prefer the textbox to validate as the user types, so UpdateSourceTrigger is set to PropertyChanged.
Is there a way to get this to work in WPF?
Looked at this question which is similar but wondering if there is a cleaner solution?
My XAML is:
<TextBox Text="{Binding Path=MyBindingPath,
StringFormat='\{0} -HELLO',
TargetNullValue={x:Static sys:String.Empty},
ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}"/>
You can set UpdateSourceTrigger to Explicit and in the TextChanged event handler of the TextBox you can call UpdateSource explicitly after performing the things you want.
//write the code you want to run first and then the following code
BindingExpression exp = this.textBox1.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
This seems to be the cleanest approach I have come up with.
This essentially sets events that will turn off the string formatting when the user has focus on the textbox, and restores the stringformat when the user leaves the textbox.
<TextBox Text="{Binding ValueToBind, UpdateSourceTrigger=PropertyChanged}" utilities:Formatting.StringFormat="N2" />
public class Formatting
{
public static readonly DependencyProperty StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat", typeof(string), typeof(Formatting), new PropertyMetadata("", OnStringFormatPropertyChanged));
private static void OnStringFormatPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element && e.OldValue != e.NewValue)
{
element.Loaded += (sender, args) =>
{
// Debug.Print("Loaded");
UpdateStringFormat(element.IsFocused ? null : (string)e.NewValue, element);
};
element.LostFocus += (sender, args) =>
{
// Debug.Print("Lost Focus");
UpdateStringFormat((string)e.NewValue, element);
};
element.GotFocus += (sender, args) =>
{
// Debug.Print("Got Focus");
UpdateStringFormat(null, element);
};
}
}
private static void UpdateStringFormat(string stringFormat, FrameworkElement element)
{
var bindingExpression = element.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null && bindingExpression.ParentBinding != null)
{
Binding parentBinding = bindingExpression.ParentBinding;
if (parentBinding.StringFormat == stringFormat)
return;
// Debug.Print("Updating String Format");
bindingExpression.UpdateSource();
Binding newBinding = new Binding
{
Path = parentBinding.Path,
Mode = parentBinding.Mode,
UpdateSourceTrigger = parentBinding.UpdateSourceTrigger,
StringFormat = stringFormat
};
element.SetBinding(TextBox.TextProperty, newBinding);
}
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static void SetStringFormat(DependencyObject obj, string stringFormat)
{
obj.SetValue(StringFormatProperty, stringFormat);
}
[AttachedPropertyBrowsableForType(typeof(UIElement))]
public static string GetStringFormat(DependencyObject obj)
{
return (string)obj.GetValue(StringFormatProperty);
}
}

Binding to ActualWidth does not work

In a Silverlight 3.0 application I'm attempting to create a rectangle in a canvas and have it stretch the whole width of the canvas. I have attempted to do this by binding to the ActualWidth property of a parent container (seem sample below), however while I don't see any binding errors the value is not being bound. The rectangle is not visible as its width is zero. In addition tried binding to the ActualWidth of the canvas that contains my rectangle but this made no difference.
I did find this bug logged on Microsoft Connect but there were no workarounds listed.
Has anyone been able to solve this issue or can they point to solution?
Edit: The original code sample was not accurate of what I'm trying to achieve, updated for more clarity.
<UserControl>
<Border BorderBrush="White"
BorderThickness="1"
CornerRadius="4"
HorizontalAlignment="Center">
<Grid x:Name="GridContainer">
<Rectangle Fill="Aqua"
Width="150"
Height="400" />
<Canvas>
<Rectangle Width="{Binding Path=ActualWidth, ElementName=GridContainer}"
Height="30"
Fill="Red" />
</Canvas>
<StackPanel>
<!-- other elements here -->
</StackPanel>
</Grid>
</Border>
</UserControl>
What are you trying to do that requires you to databind to the ActualWidth property?
This is a known issue with Silverlight, and there is no simple workaround.
One thing that could be done is to set up the visual tree in such a way that you do not need to actually set the Width of the Rectangle, and just allow it to stretch to the appropriate size. So in the example above, if you remove the Canvas (or change the Canvas to some other Panel) and leave the Rectangle's HorizontalAlignment set to Stretch, it will take up all of the available width (effectively the Width of the Grid).
However, this may not be possible in your particular case, and it may really be necessary to set up the databinding. It has already been established that this is not possible directly, but with the help of a proxy object, we can set up the required binding. Consider this code:
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public double ActualHeightValue
{
get{ return Element == null? 0: Element.ActualHeight; }
}
public double ActualWidthValue
{
get { return Element == null ? 0 : Element.ActualWidth; }
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy),
new PropertyMetadata(null,OnElementPropertyChanged));
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ActualSizePropertyProxy)d).OnElementChanged(e);
}
private void OnElementChanged(DependencyPropertyChangedEventArgs e)
{
FrameworkElement oldElement = (FrameworkElement)e.OldValue;
FrameworkElement newElement = (FrameworkElement)e.NewValue;
newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
if (oldElement != null)
{
oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
}
NotifyPropChange();
}
private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
{
NotifyPropChange();
}
private void NotifyPropChange()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
}
}
}
We can use this in xaml as follows:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<c:ActualSizePropertyProxy Element="{Binding ElementName=LayoutRoot}" x:Name="proxy" />
</Grid.Resources>
<TextBlock x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" />
</Grid>
So we are Binding TextBlock.Text to the ActualWidthValue on the proxy object. The proxy object in turn provides the ActualWidth of the Element, which is provided by another Binding.
This is not a simple solution to the problem, but it is the best that I can think of for how to databind to ActualWidth.
If you explained your scenario a bit more, it may be possible to come up with a simpler solution. DataBinding may not be required at all; would it be possible to just set the property from code in a SizeChanged event handler?
Using the mechanism of attached properties, properties which represent ActualHeight and ActualWidth and are updated by SizeChanged event can be defined. Its usage will look like the following.
<Grid local:SizeChange.IsEnabled="True" x:Name="grid1">...</Grid>
<TextBlock Text="{Binding ElementName=grid1,
Path=(local:SizeChange.ActualHeight)}"/>
Technical details can be found at the following:
http://darutk-oboegaki.blogspot.com/2011/07/binding-actualheight-and-actualwidth.html
The advantage of this solution compared to others is in that the attached properties defined in the solution (SizeChange.ActualHeight and SizeChange.ActualWidth) can be used for any FrameworkElement without creating any sub class. This solution is reusable and less invasive.
In the event that the link becomes stale, here is the SizeChange Class as shown on the link:
// Declare SizeChange class as a sub class of DependencyObject
// because we need to register attached properties.
public class SizeChange : DependencyObject
{
#region Attached property "IsEnabled"
// The name of IsEnabled property.
public const string IsEnabledPropertyName = "IsEnabled";
// Register an attached property named "IsEnabled".
// Note that OnIsEnabledChanged method is called when
// the value of IsEnabled property is changed.
public static readonly DependencyProperty IsEnabledProperty
= DependencyProperty.RegisterAttached(
IsEnabledPropertyName,
typeof(bool),
typeof(SizeChange),
new PropertyMetadata(false, OnIsEnabledChanged));
// Getter of IsEnabled property. The name of this method
// should not be changed because the dependency system
// uses it.
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
// Setter of IsEnabled property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
#endregion
#region Attached property "ActualHeight"
// The name of ActualHeight property.
public const string ActualHeightPropertyName = "ActualHeight";
// Register an attached property named "ActualHeight".
// The value of this property is updated When SizeChanged
// event is raised.
public static readonly DependencyProperty ActualHeightProperty
= DependencyProperty.RegisterAttached(
ActualHeightPropertyName,
typeof(double),
typeof(SizeChange),
null);
// Getter of ActualHeight property. The name of this method
// should not be changed because the dependency system
// uses it.
public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
// Setter of ActualHeight property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
#endregion
#region Attached property "ActualWidth"
// The name of ActualWidth property.
public const string ActualWidthPropertyName = "ActualWidth";
// Register an attached property named "ActualWidth".
// The value of this property is updated When SizeChanged
// event is raised.
public static readonly DependencyProperty ActualWidthProperty
= DependencyProperty.RegisterAttached(
ActualWidthPropertyName,
typeof(double),
typeof(SizeChange),
null);
// Getter of ActualWidth property. The name of this method
// should not be changed because the dependency system
// uses it.
public static double GetActualWidth(DependencyObject obj)
{
return (double)obj.GetValue(ActualWidthProperty);
}
// Setter of ActualWidth property. The name of this method
// should not be changed because the dependency system
// uses it.
public static void SetActualWidth(DependencyObject obj, double value)
{
obj.SetValue(ActualWidthProperty, value);
}
#endregion
// This method is called when the value of IsEnabled property
// is changed. If the new value is true, an event handler is
// added to SizeChanged event of the target element.
private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
// The given object must be a FrameworkElement instance,
// because we add an event handler to SizeChanged event
// of it.
var element = obj as FrameworkElement;
if (element == null)
{
// The given object is not an instance of FrameworkElement,
// meaning SizeChanged event is not available. So, nothing
// can be done for the object.
return;
}
// If IsEnabled=True
if (args.NewValue != null && (bool)args.NewValue == true)
{
// Attach to the element.
Attach(element);
}
else
{
// Detach from the element.
Detach(element);
}
}
private static void Attach(FrameworkElement element)
{
// Add an event handler to SizeChanged event of the element
// to take action when actual size of the element changes.
element.SizeChanged += HandleSizeChanged;
}
private static void Detach(FrameworkElement element)
{
// Remove the event handler from the element.
element.SizeChanged -= HandleSizeChanged;
}
// An event handler invoked when SizeChanged event is raised.
private static void HandleSizeChanged(object sender, SizeChangedEventArgs args)
{
var element = sender as FrameworkElement;
if (element == null)
{
return;
}
// Get the new actual height and width.
var width = args.NewSize.Width;
var height = args.NewSize.Height;
// Update values of SizeChange.ActualHeight and
// SizeChange.ActualWidth.
SetActualWidth(element, width);
SetActualHeight(element, height);
}
}
My solution is to declare my own DependencyProperty called RealWidth and update its value on the SizeChanged event. You can then bind to RealWidth, which will update, unlike the ActualWidth property.
public MyControl()
{
InitializeComponent();
SizeChanged += HandleSizeChanged;
}
public static DependencyProperty RealWidthProperty =
DependencyProperty.Register("RealWidth", typeof (double),
typeof (MyControl),
new PropertyMetadata(500D));
public double RealWidth
{
get { return (double) GetValue(RealWidthProperty); }
set { SetValue(RealWidthProperty, value); }
}
private void HandleSizeChanged(object sender, SizeChangedEventArgs e)
{
RealWidth = e.NewSize.Width;
}
Why not create a simple panel control that inherits from ContentPresenter and actually can provide the current size.
public class SizeNotifyPanel : ContentPresenter
{
public static DependencyProperty SizeProperty =
DependencyProperty.Register("Size",
typeof (Size),
typeof (SizeNotifyPanel),
null);
public Size Size
{
get { return (Size) GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public SizeNotifyPanel()
{
SizeChanged += (s, e) => Size = e.NewSize;
}
}
It should then be used as wrapper for the actual content.
<local:SizeNotifyPanel x:Name="Content">
<TextBlock Text="{Binding Size.Height, ElementName=Content}" />
</local:SizeNotifyPanel>
Worked for me like a charm and looks clean.
Based on #darutk's answer, here's an attached property-based solution which does the job very elegantly.
public static class SizeBindings
{
public static readonly DependencyProperty ActualHeightProperty =
DependencyProperty.RegisterAttached("ActualHeight", typeof (double), typeof (SizeBindings),
new PropertyMetadata(0.0));
public static readonly DependencyProperty ActualWidthProperty =
DependencyProperty.RegisterAttached("ActualWidth", typeof (Double), typeof (SizeBindings),
new PropertyMetadata(0.0));
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (SizeBindings),
new PropertyMetadata(false, HandlePropertyChanged));
private static void HandlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as FrameworkElement;
if (element == null)
{
return;
}
if ((bool) e.NewValue == false)
{
element.SizeChanged -= HandleSizeChanged;
}
else
{
element.SizeChanged += HandleSizeChanged;
}
}
private static void HandleSizeChanged(object sender, SizeChangedEventArgs e)
{
var element = sender as FrameworkElement;
SetActualHeight(element, e.NewSize.Height);
SetActualWidth(element, e.NewSize.Width);
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
public static Double GetActualWidth(DependencyObject obj)
{
return (Double) obj.GetValue(ActualWidthProperty);
}
public static void SetActualWidth(DependencyObject obj, Double value)
{
obj.SetValue(ActualWidthProperty, value);
}
public static double GetActualHeight(DependencyObject obj)
{
return (double)obj.GetValue(ActualHeightProperty);
}
public static void SetActualHeight(DependencyObject obj, double value)
{
obj.SetValue(ActualHeightProperty, value);
}
}
Use it like this:
<Grid>
<Border x:Name="Border" behaviors:SizeBindings.IsEnabled="True"/>
<Border MinWidth="{Binding (behaviors:SizeBindings.ActualWidth), ElementName=Border}"/>
</Grid>
I've tested the updated xaml that you publishing using a TestConverter to see what value gets passed to the width and it is working for me (I am using VS 2010 B2). To use the TestConverter just set a breakpoint in the Convert method.
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
A value of 150 was passed in and the Rectangle had a width of 150.
Were you expecting something different?
This is an as an aside answer which may help someone for binding to the ActualWidth.
My process didn't need a change event, it needed an end result of a value in its current state. So I created a dependency property called Target on my custom control/process as a FrameworkElement and the consumer xaml would bind to the actual object in question.
When it was time for the calculation the code could pulled the actual object and extracted it's ActualWidth from it.
Dependency Property on Control
public FrameworkElement Target
{
get { return (FrameworkElement)GetValue(TargetProperty);}
set { SetValue(TargetProperty, value);}
}
// Using a DependencyProperty as the backing store for Target.
// This enables animation, styling, binding, general access etc...
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(FrameworkElement),
typeof(ThicknessWrapper),
new PropertyMetadata(null, OnTargetChanged));
XAML on Consumer side showing a binding to a Rectangle
<local:ThicknessWrapper Target="{Binding ElementName=thePanel}"/>
<Rectangle x:Name="thePanel" HorizontalAlignment="Stretch" Height="20" Fill="Blue"/>
Code to Acquire
double width;
if (Target != null)
width = Target.ActualWidth; // Gets the current value.
Based on KeithMahoney's answer, it works fine on my UWP App and solves my problem. However, I cannot see my control in Design time because both the initial values of ActualWidthValue and ActualHeightValue are not provided in Design time. Although it works fine in running time, it is inconvenient for designing the layout of my control. With a little modification, this problem can be solved.
In his c# code for both properties ActualWidthValue and ActualHeightValue, add
set {;}
to let us can provide dummy values from XAML code. Although it is no use for running time, it can be used for design time.
In the declaration of Resources of his XAML code, provide c:ActualSizePropertyProxy suitable values for ActualWidthValue and ActualHeightValue such as
ActualHeightValue="800" ActualWidthValue="400"
Then it will show you a 400x800 control in design time.

Resources