Execute a method on second control from the first control in WPF - wpf

My ViewControl has a method called ZoomIn(). How can I execute this method on the ViewControl by clicking a Button control without going to the code-behind?
<controls:ViewControl/>
<Button Content="Zoom In"/>
Here is the code for ViewControl.xaml.cs:
public void ZoomIn()
{
double actualWidth = m_child.ActualWidth;
double actualHeight = m_child.ActualHeight;
double x = (0.5 * actualWidth - Dx) / Scale;
double y = (0.5 * actualHeight - Dy) / Scale;
float startScale = Scale;
Scale = Math.Min(Scale * ZoomFactor, ZoomMax);
Dx = (float)x * (startScale - Scale) + Dx;
Dy = (float)y * (startScale - Scale) + Dy;
}
I am trying to use MVVM for my design, but I'm not sure how to do this in this scenario because the ZoomIn() method is related to the View.
A similar situation would be if I had a Button and a TextBox, and I wanted to call the SelectAll() method on the TextBox when the Button is clicked. How can I do this without using the code-behind?

There are actually several different ways to do this, one solution is to bind to an event using a behavior and a wrapper class. First define a wrapper for the event that your view model will trigger:
public class EventTriggerWrapper
{
public event EventHandler OnTriggered;
public void Trigger()
{
this.OnTriggered?.Invoke(this, EventArgs.Empty);
}
}
For the purpose of demonstration here's some XAML of a button and a WebBrowser, I'll use an instance of the wrapper in the view model to trigger the web broswer's Navigate() function whenever the button is pressed:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Content="Click Me" Command="{Binding NavigateCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10" />
<WebBrowser Grid.Row="1">
<i:Interaction.Behaviors>
<behaviors:MyCustomBehavior EventTrigger="{Binding EventTrigger}" />
</i:Interaction.Behaviors>
</WebBrowser>
</Grid>
You can see that I've added a custom behaviour to the web browser control, and it's bound to a view model property called EventTrigger. You'll need to add this along with a command handler for the button to your view model:
public class MainViewModel
{
public EventTriggerWrapper EventTrigger { get; } = new EventTriggerWrapper();
private ICommand _NavigateCommand;
public ICommand NavigateCommand => this._NavigateCommand ?? (this._NavigateCommand = new RelayCommand(OnNavigate));
private void OnNavigate()
{
this.EventTrigger.Trigger();
}
}
So all that's left it to create the behavior with a property that subscribes to the event and then calls whatever function in your target control you want:
public class MyCustomBehavior : Behavior<WebBrowser>
{
public EventTriggerWrapper EventTrigger
{
get { return (EventTriggerWrapper)GetValue(EventTriggerProperty); }
set { SetValue(EventTriggerProperty, value); }
}
// Using a DependencyProperty as the backing store for EventTrigger. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EventTriggerProperty =
DependencyProperty.Register("EventTrigger", typeof(EventTriggerWrapper), typeof(MyCustomBehavior), new PropertyMetadata(null, OnEventTriggerChanged));
private static void OnEventTriggerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behaviour = d as MyCustomBehavior;
var oldValue = e.OldValue as EventTriggerWrapper;
if (oldValue != null)
oldValue.OnTriggered -= behaviour.OnEventTriggered;
var newValue = e.NewValue as EventTriggerWrapper;
if (newValue != null)
newValue.OnTriggered += behaviour.OnEventTriggered;
}
private void OnEventTriggered(object sender, EventArgs e)
{
if (this.AssociatedObject != null)
this.AssociatedObject.Navigate("http://www.google.com"); // <-- change this to the function you want to invoke
}
}

Related

Binding two dependency properties from two different usercontrols

I have created two separate usercontrols, they are meant to work together.
The first one is a simple usercontrol with a thumb attached to it, the thumb makes the control move around by dragging, this is simple and working.
XAML:
<Canvas>
<Thumb x:Name="Thumb" Width="15" Height="15" DragDelta="Thumb_DragDelta"/>
</Canvas>
Code-Behind: A dependency property called Position, when Setter is called it updates the usercontrol's margin.
public partial class ThumbPoint : UserControl
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); this.Margin = new Thickness(value.X, value.Y, 0, 0); }
}
// Using a DependencyProperty as the backing store for Position. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(ThumbPoint), new PropertyMetadata(new Point()));
public ThumbPoint()
{
InitializeComponent();
Position = new Point(0, 0);
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Position = new Point(Position.X + e.HorizontalChange, Position.Y + e.VerticalChange);
}
}
The second UserControl is called StraightLine, its composed of a Line control
XAML:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"/>
</Canvas>
Code-Behind: A dependency property called StartPosition, when Setter is called it updates the Line X1 and Y1 (starting position of the line).
public partial class StraightLine : UserControl
{
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); Line.X1 = value.X; Line.Y1 = value.Y; }
}
// Using a DependencyProperty as the backing store for StartPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point()));
public StraightLine()
{
InitializeComponent();
Line.X1 = 0;
Line.Y1 = 0;
Line.X2 = 300;
Line.Y2 = 200;
}
}
Here I am trying to bind them together on the mainwindow.xaml:
<Canvas>
<local:ThumbPoint x:Name="ThumbPoint"/>
<local:StraightLine StartPosition="{Binding Position, ElementName=ThumbPoint}"/>
</Canvas>
Desired effect: DependencyProperty StartPosition of the StraightLine should be updated.
Whats happening: It's not being updated so only the ThumbPoint is moving.
binding doesn't use common property wrappers for DP (public Point StartPosition), it uses SetValue() directly, so code in setter isn't invoked.
What is needed is propertyChangedCallback:
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); }
}
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point(), OnStartPositionChanged));
private static void OnStartPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StraightLine c = (StraightLine) d;
c.Line.X1 = c.StartPosition.X;
c.Line.Y1 = c.StartPosition.Y;
}
in ThumbPoint public Point Position property has the same issue but it works there because you use setter directly: Position = new Point()
Alternatively bind X1 and Y1 value in xaml:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"
X1="{Binding StartPosition.X, RelativeSource={RelativeSource AncestorType=StraightLine}}"
Y1="{Binding StartPosition.Y, RelativeSource={RelativeSource AncestorType=StraightLine}}"/>
</Canvas>
(or use ElementName instead of RelativeSource)

Add trigger to every UserControl

My application has about 15 different UserControls that are uniformly loaded into a content-area at runtime.
My project is 100% MVVM-compliant, so I have the following XAML inserted into every UserControl's XAML:
<UserControl
...
xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
>
<intr:Interaction.Triggers>
<intr:EventTrigger EventName="Loaded">
<intr:InvokeCommandAction Command="{Binding ViewLoadedCommand}"/>
</intr:EventTrigger>
</intr:Interaction.Triggers>
<!-- Rest of UserControl content here -->
</UserControl>
Obviously this isn't ideal and is also a violation of DRY.
What is the best way to have this XAML applied to this set of UserControls? (but not every UserControl, so a simple <Style Type="UserControl" /> would be inappropriate).
I use behavior implemented as attached property. It has two major advantages over System.Windows.Interactivity:
it can be defined in style.
much less xaml code in the views
in your case, the view could look like:
<UserControl ...
my:AttachedCommands.LoadedCommand="{Binding ViewLoadedCommand}">
In my solution, I'm not using commands, but I call methods on viewmodel if the viewmodel implements IViewModelLifeCycle interface:
public interface IViewModelLifeCycle
{
void Activate(object extraData);
void Deactivate();
}
All my views uses this style:
<Style x:Key="ViewBaseStyle">
<Setter Property="my:ViewModelLifeCycleBehavior.ActivateOnLoad" Value="True" />
and the behavior:
public static class ViewModelLifeCycleBehavior
{
public static readonly DependencyProperty ActivateOnLoadProperty = DependencyProperty.RegisterAttached("ActivateOnLoad", typeof (bool), typeof (ViewModelLifeCycleBehavior),
new PropertyMetadata(ActivateOnLoadPropertyChanged));
public static void SetActivateOnLoad(FrameworkElement element, bool value)
{
element.SetValue(ActivateOnLoadProperty, value);
}
public static bool GetActivateOnLoad(FrameworkElement element)
{
return (bool)element.GetValue(ActivateOnLoadProperty);
}
private static void ActivateOnLoadPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (DesignerProperties.GetIsInDesignMode(obj)) return;
var element = (FrameworkElement)obj;
element.Loaded -= ElementLoaded;
element.Unloaded -= ElementUnloaded;
if ((bool) args.NewValue == true)
{
element.Loaded += ElementLoaded;
element.Unloaded += ElementUnloaded;
}
}
static void ElementLoaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement) sender;
var viewModel = (IViewModelLifeCycle) element.DataContext;
if (viewModel == null)
{
DependencyPropertyChangedEventHandler dataContextChanged = null;
dataContextChanged = (o, _e) =>
{
ElementLoaded(sender, e);
element.DataContextChanged -= dataContextChanged;
};
element.DataContextChanged += dataContextChanged;
}
else if (element.ActualHeight > 0 && element.ActualWidth > 0) //to avoid activating twice since loaded event is called twice on TabItems' subtrees
{
viewModel.Activate(null);
}
}
private static void ElementUnloaded(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
var viewModel = (IViewModelLifeCycle)element.DataContext;
viewModel.Deactivate();
}
}
TIP:
Create your custom Item Template in Visual Studio for View and ViewModel. its very easy and saves a lot of time. The item template can contain xaml code with the trigger/behaviour, pointing to your base style, your d:DataContext definition and your viewmodel class.
you could create a "base-load" Usercontrol with the Interaction.Triggers and just put a ContentPresenter in it where you bind the real content to.
<UserControl x:class="OneOfMyOtherControls">
<MyBaseUserControl>
<!-- your UserControl Content-->
</MyBaseUserControl>
</UserControl>

DependencyProperty ObservableCollection event

I am trying to make UserControl (GridSearch) which has another UserControl_2 inside. I want to add some FrameworkElements to panel of UserControl_2 using XAML.
So I did ObservableCollection DependencyProperty in GridSearch:
public partial class GridSearch : UserControl
{
public GridSearch()
{
InitializeComponent();
}
public ObservableCollection<Filter> Filters
{
get { return (ObservableCollection<Filter>)GetValue(FiltersProperty); }
set { SetValue(FiltersProperty, value); }
}
public static readonly DependencyProperty FiltersProperty =
DependencyProperty.Register("Filters",
typeof(ObservableCollection<Filter>),
typeof(GridSearch),
new FrameworkPropertyMetadata(getObservableFilters(), null)
);
private static ObservableCollection<Filter> getObservableFilters()
{
var ob = new ObservableCollection<Filter>();
ob.CollectionChanged += ob_CollectionChanged;
return ob;
}
private static void ob_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
}
Now I was trying to add new elements to the panel by using ob_CollectionChanged. However because it is static method I cannot access the panel. I cannot cast the sender because it gives me only the ObservableCollection. However I need GridSearch.
I am looking for solution from several hours and I am not able to find any idea how to solve it.
Change the getObservableFilters() method to just create and return the observable collection.
And in the GridSearch() constructor, after the call to InitializeComponent(), you can add a handler for Filters.CollectionChanged and provide a non-static member function.
Ok it works finally the key thing was to create new ObservableCollection() in constructor for each instance of the control.
However there is still one problem. Everything works at runtime but designer is not able to display anything as it gets following error:
Object reference not set to an instance of an object.
and this is tabout this line: <gsh:GridSearch.Filters>
This is the code I end up with:
public partial class GridSearch : UserControl
{
public GridSearch()
{
Filters = new ObservableCollection<Label>();
InitializeComponent();
Filters.CollectionChanged += Filters_CollectionChanged;
}
void Filters_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (Label uc in e.NewItems)
pnlFilters.Children.Add(uc);
}
public ObservableCollection<Label> Filters
{
get { return (ObservableCollection<Label>)GetValue(FiltersProperty); }
set { SetValue(FiltersProperty, value); }
}
public static readonly DependencyProperty FiltersProperty =
DependencyProperty.Register("Filters",
typeof(ObservableCollection<Label>),
typeof(GridSearch),
new FrameworkPropertyMetadata(new ObservableCollection<Label>(), null)
);
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<gsh:GridSearch>
<gsh:GridSearch.Filters>
<Label Content="aa" />
<Label Content="aa" />
<Label Content="aa" />
</gsh:GridSearch.Filters>
</gsh:GridSearch>
<gsh:GridSearch Grid.Row="1">
<gsh:GridSearch.Filters>
<Label Content="bb" />
<Label Content="cc" />
<Label Content="dd" />
</gsh:GridSearch.Filters>
</gsh:GridSearch>
</Grid>

How do I create a new instance of a control in the view without violating MVVM

I am encountering a similar problem to what is described in this SO question. The suggested solution is to create a new WebBrowser Control for each now page (PDF) we wish to present (Overwriting the old WebBrowser control).
What is the correct way of creating a new control like that in MVVM? I trying to keep the VM ignorant about the implementation of the view.
Why does the VM need to know? Why can't the view just hook into an appropriate event (define one if you like, or just use the PropertyChanged) and recreate the control?
Create an interface in the ViewModel named IBrowserCreator, with a method called CreateBrowser().
Create a static class in the ViewModel named ViewHelper, and add to it a static property of type IBrowserCreator named BrowserCreator.
In the View layer, create a new class called BrowserCreator, which implements ViewModel.IBrowserCreator.
In the View initialization code, instantiate a BrowserCreator, and assign it to ViewModel.ViewHelper.BrowserCreator.
From your ViewModel, you should now be able to call:
ViewHelper.BrowserCreator.CreateBrowser()
Obviously this answer is a framework only, but it should give you the general idea. You'll need to implement the CreateBrowser method to suit your exact needs.
why not simply use a Datatemplate and let WPF do the rest?
create a usercontrol with the webbrowser. you have to add an attached property because you can not bind to source directly.
<UserControl x:Class="WpfBrowser.BrowserControl"
xmlns:WpfBrowser="clr-namespace:WpfBrowser" >
<Grid>
<WebBrowser WpfBrowser:WebBrowserUtility.BindableSource="{Binding MyPdf}"/>
</Grid>
</UserControl>
create a viewmodel which handle your uri
public class MyPdfVM
{
public Uri MyPdf { get; set; }
public MyPdfVM()
{
this.MyPdf = new Uri(#"mypdf path");
}
}
take your pageviewmodel, add the pdfviewmodel and take a contentcontrol in your view
public class MyPageViewmodel: INotifyPropertyChanged
{
private MyPdfVM _myPdfStuff;
public MyPdfVM MyPdfStuff
{
get { return _myPdfStuff; }
set { _myPdfStuff = value; this.NotifyPropertyChanged(()=>this.MyPdfStuff);}
}
public MyViewmodel()
{
this.MyPdfStuff = new MyPdfVM();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(Expression<Func<T>> property)
{
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
}
window.xaml
<Window x:Class="WpfBrowser.MainWindow"
xmlns:WpfBrowser="clr-namespace:WpfBrowser"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type WpfBrowser:MyPdfVM}">
<WpfBrowser:BrowserControl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="64*" />
<RowDefinition Height="247*" />
</Grid.RowDefinitions>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="32,14,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<ContentControl Grid.Row="1" Content="{Binding MyPdfStuff}"/>
</Grid>
</Window>
window.xaml.cs
public partial class MainWindow : Window
{
private MyViewmodel _data;
public MainWindow()
{
_data = new MyViewmodel();
InitializeComponent();
this.DataContext = _data;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this._data.MyPdfStuff = new MyPdfVM() { MyPdf = new Uri(#"your other pdf path for testing") };
}
}
when ever you change the MyPdfStuff Property the webbroswer update the pdf.
attached property
public static class WebBrowserUtility
{
public static readonly DependencyProperty BindableSourceProperty =
DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(WebBrowserUtility), new UIPropertyMetadata(null, BindableSourcePropertyChanged));
public static string GetBindableSource(DependencyObject obj)
{
return (string)obj.GetValue(BindableSourceProperty);
}
public static void SetBindableSource(DependencyObject obj, string value)
{
obj.SetValue(BindableSourceProperty, value);
}
public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = o as WebBrowser;
if (browser != null)
{
string uri = e.NewValue as string;
browser.Source = string.IsNullOrWhiteSpace(uri) ? null:new Uri(uri);
}
}
}
EDIT: added some code so you can see that if you chane the PDFViewmodel your browsercontrol show the new pdf.

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