Canvas has element with adorner. Adorner uses VisualCollection, and places a thumb to specific place of the adorned element. I would like to click to the adorner, hanlde PreviewMouseLeftButtonDown event of the canvas and get the underlying element inside the adorner. But I get MainWindow as e.Source in this case. Could you please help? I'm almost newbie in WPF.
public class ConnectorAdorner : Adorner
{
private readonly ConnectorThumb _thumb;
private readonly VisualCollection _visuals;
public ConnectorAdorner(UIElement adornedElement) : base(adornedElement)
{
Focusable = true;
_visuals = new VisualCollection(this);
_thumb = new ConnectorThumb();
_thumb.Background = Brushes.Transparent;
_visuals.Add(_thumb);
}
protected override Size ArrangeOverride(Size finalSize)
{
double elHeight = AdornedElement.DesiredSize.Height;
double adornerWidth = DesiredSize.Width;
double adornerHeight = DesiredSize.Height;
_thumb.Arrange(new Rect(0, -elHeight/2, adornerWidth, adornerHeight));
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get { return _visuals.Count; }
}
}
public class Shape : UserControl
{
private AdornerLayer _adornerLayer;
public Shape()
{
Width = 50;
Height = 50;
Background = Brushes.Tomato;
BorderThickness = new Thickness(2);
BorderBrush = Brushes.Black;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
if (_adornerLayer != null)
{
ConnectorAdorner adorner = new ConnectorAdorner(this);
_adornerLayer.Add(adorner);
}
}
}
public class ConnectorThumb : Thumb
{
public ConnectorThumb()
{
Width = 20;
Height = 20;
Background = Brushes.Transparent;
Foreground = Brushes.Black;
BorderThickness = new Thickness(1);
IsHitTestVisible = true;
}
}
public partial class MainWindow : Window
{
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// I'd like to get ConnectionThumb here..
}
}
While it is hard to say without seeing your code, but I think using the Canvas.ZIndex property on the elements (Adorner) will ensure that the elements are layered the way your want.
Related
I am trying to implement some fade-in and fade-out animations for a user control in WPF. For the fade-in animation I was able to use the Loaded event to accomplish that.
public sealed partial class NowPlayingView : UserControl
{
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(NowPlayingView), new PropertyMetadata(Duration.Automatic));
public NowPlayingView()
{
Opacity = 0;
InitializeComponent();
Loaded += NowPlayingView_Loaded;
Unloaded += NowPlayingView_Unloaded;
}
private void NowPlayingView_Unloaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(1.0, 0.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new (0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
}
I attempted to use the Unloaded event for the fade-out effect only to find out that the event is fired after the UserControl is removed from the visual tree (when the UserControl is no longer visible or accessible). Is there a way to run some code right before the UserControl "closes", something like the OnClosing event of a Window?
EDIT:
For a bit more context, the UserControl acts as a component of a more complex window. It is activated whenever the Property NowPlayingViewModel is not null and deactivated when null (which I do in order to hide the UserControl). It is when I set the ViewModel to null that I want to run the fade-out animation and I would like to keep the code-behind decoupled from other ViewModel logic.
<!-- Now playing View-->
<ContentControl Grid.RowSpan="3" Grid.ColumnSpan="2" Content="{Binding NowPlayingViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:NowPlayingViewModel}">
<views:NowPlayingView AnimationDuration="00:00:00.8" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
From my testing, I couldn't find any good solution to this so far, though I am open to suggestions that lead to similar behavior.
There is no Closing event in UserControl.. but you can get the parent window when UserControl is loaded and implement the fade-out behavior there..
First, Remove Unloaded += NowPlayingView_Unloaded;
Then, modify the Loaded code a bit..
private Window ParentWindow
{
get
{
DependencyObject parentDepObj = this;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is Window parentWindow) return parentWindow;
} while (parentDepObj != null);
return null;
}
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
var parentWindow = this.ParentOfType<Window>();
parentWindow.Closing += WindowClosing;
}
private void WindowClosing(object sender, CancelEventArgs args)
{
var pw = ParentWindow;
pw.Closing -= WindowClosing;
args.Cancel = true;
var anim = new(1.0, 0.0, AnimationDuration);
anim.Completed += (s, _) => pw.Close();
BeginAnimation(OpacityProperty, anim);
}
Optional Note. You could replace the getter of ParentWindow property with a simple call
private Window ParentWindow => this.ParentOfType<Window>();
Where ParentOfType is an extension function in some public static class Utilities..
public static T ParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
var parentDepObj = child;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is T parent) return parent;
} while (parentDepObj != null);
return null;
}
I'm trying to apply a Translate transform to my Canvas using attached properties - the canvas moves as I drag it but is very jumpy and tries to jump back to the original position constantly. When I scroll out of my canvas it also completely messes up and drags the canvas off screen.
My attached property class methods:
private Point _originalMouseDownPoint;
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs) {
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
var canvas = sender as Canvas;
if (BaseViewModel.Mode != MouseHandlingModeEnum.Panning) return;
var translateTransform = new
TranslateTransform(pos.X - _originalMouseDownPoint.X, pos.Y - _originalMouseDownPoint.Y);
canvas.RenderTransform = translateTransform;
}
private void AssociatedObjectOnMouseDown(object sender, MouseButtonEventArgs e) {
var canvas = sender as Canvas;
canvas.CaptureMouse();
canvas.Focus();
_originalMouseDownPoint = e.GetPosition(canvas);
BaseViewModel.Mode = MouseHandlingModeEnum.Panning;
}
private void AssociatedObjectOnMouseUp(object sender, MouseButtonEventArgs e) {
var canvas = sender as Canvas;
canvas.ReleaseMouseCapture();
BaseViewModel.Mode = MouseHandlingModeEnum.None;
}
And then my XAML simply sets this up through the canvas behaviours.
<i:Interaction.Behaviors>
<attachedProperties:MouseBehaviour MouseX="{Binding PanelX , Mode=OneWayToSource}"
MouseY="{Binding PanelY, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
(Ignore the Bindings, they are for a seperate functionality.
The program should smoothly pan across the canvas. I've seen some people use a matrix transform, should I try and implement that in some way?
I found in my experience that i like working with the Canvas.Left/Top when i only need to drag or move objects in a simple way. I use the transforms when i am doing something more complex (which is rare). It is usually easier to debug and more streamlined.
You can do something like this:
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs) {
var pos = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = pos.X;
MouseY = pos.Y;
var canvas = sender as Canvas;
if (BaseViewModel.Mode != MouseHandlingModeEnum.Panning) return;
Canvas.SetLeft(AssociatedObject, MouseX - _originalMouseDownPoint.X);
Canvas.SetTop(AssociatedObject, MouseY - _originalMouseDownPoint.Y);
}
private void AssociatedObjectOnMouseDown(object sender, MouseButtonEventArgs e) {
var canvas = sender as Canvas;
canvas.CaptureMouse();
canvas.Focus();
_originalMouseDownPoint = e.GetPosition((UIElement)sender);
BaseViewModel.Mode = MouseHandlingModeEnum.Panning;
}
private void AssociatedObjectOnMouseUp(object sender, MouseButtonEventArgs e) {
var canvas = sender as Canvas;
canvas.ReleaseMouseCapture();
_originalMouseDownPoint = null;
BaseViewModel.Mode = MouseHandlingModeEnum.None;
}
I am assuming your BaseViewModel.Mode is a flag for indicating a dragging condition.
If you want to bind your VM to the dragging you could add Properties for the Position and access the DataContext directly (var vm = DataContext as AssociatedObjectViewModel) and set the new positions. Then in the XAML you can bind the Canvas.Left and Canvas.Top to these properties. However, this isnt always possible depending on how you set everything up, and sometimes it is just easier/cleaner to take care of this in Code Behind.
public class MouseBehaviour : Behavior<Panel> {
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
private Point _originalMouseDownPoint;
private double _hOff = 1;
private double _vOff = 1;
public double MouseY {
get => (double)GetValue(MouseYProperty);
set => SetValue(MouseYProperty, value);
}
public double MouseX {
get => (double)GetValue(MouseXProperty);
set => SetValue(MouseXProperty, value);
}
protected override void OnAttached() {
AssociatedObject.MouseUp += AssociatedObjectOnMouseUp;
AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
AssociatedObject.MouseDown += AssociatedObjectOnMouseDown;
}
protected override void OnDetaching()
{
AssociatedObject.MouseUp -= AssociatedObjectOnMouseUp;
AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
AssociatedObject.MouseDown -= AssociatedObjectOnMouseDown;
}
/// <summary>
/// Deals with mouse movement event
/// </summary>
/// <param name="sender"></param>
/// <param name="mouseEventArgs"></param>oop
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs) {
//Keep track of position of mouse on the canvas
var canvasPosition = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = canvasPosition.X;
MouseY = canvasPosition.Y;
//If we're currently panning
var canvas = sender as Canvas;
var scrollViewer = canvas.Parent as ScrollViewer;
//Position of the scroll viewer
var scrollPosition = mouseEventArgs.GetPosition(scrollViewer);
if (BaseViewModel.Mode != MouseHandlingModeEnum.Panning) return;
//Then update the scroll viewer based on our mouse movement
scrollViewer.ScrollToHorizontalOffset(
_hOff + (_originalMouseDownPoint.X - scrollPosition.X));
scrollViewer.ScrollToVerticalOffset(_vOff + (_originalMouseDownPoint.Y - scrollPosition.Y));
}
/// <summary>
/// Deals with the mouse down event on the canvas
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void AssociatedObjectOnMouseDown(object sender, MouseButtonEventArgs e)
{
var canvas = sender as Canvas;
var scrollViewer = canvas.Parent as ScrollViewer;
//Gives the current offset of the scroll viewer
_hOff = scrollViewer.HorizontalOffset;
_vOff = scrollViewer.VerticalOffset;
_originalMouseDownPoint = e.GetPosition(scrollViewer);
BaseViewModel.Mode = MouseHandlingModeEnum.Panning;
canvas.CaptureMouse();
canvas.Focus();
}
/// <summary>
/// Deals with mouse up event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void AssociatedObjectOnMouseUp(object sender, MouseButtonEventArgs e)
{
var canvas = sender as Canvas;
canvas.ReleaseMouseCapture();
BaseViewModel.Mode = MouseHandlingModeEnum.None;
}
}
For future readers, I ended up using my ScrollViewer that my Canvas is wrapped in to Pan across the canvas instead, and it seems to be working quite nicely now.
I want to Implement special button and i don't know even how to start with this.
I want my Button's content property to be: Play. When clicking on it, I want 2 other Buttons to pop up in the left and in the right sides: Single Play and Parallel Play
All you have to do is to create your 3 buttons and then put a visibility converter on your 2 sides buttons. Create a property that will hold if they should be visible or not and bind the visibility converter to this property. The Play button should modify this property when clicked.
I hope this gives you an idea on how to start with this.
After a lot of discussion, here is the result to solve this problem:
xaml:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfApplication3"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Horizontal">
<Button Name="btnSinglePlay" Visibility="Collapsed" my:VisibilityAnimation.IsActive="True">SinglePlay</Button>
<Button Name="btnPlay" Click="btnPlay_Click">Play</Button>
<Button Name="btnParallelPlay" Visibility="Collapsed" my:VisibilityAnimation.IsActive="True">ParallelPlay</Button>
</StackPanel>
</Grid>
C# to set the 2 sides button visible.
private void btnPlay_Click(object sender, RoutedEventArgs e)
{
btnSinglePlay.Visibility = Visibility.Visible;
btnParallelPlay.Visibility = Visibility.Visible;
}
And the c# code to permit the fade in/fade out. It comes from WPF Fade Animation so props to Anvaka, not to me.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Animation;
namespace WpfApplication3
{
public class VisibilityAnimation : DependencyObject
{
private const int DURATION_MS = 200;
private static readonly Hashtable _hookedElements = new Hashtable();
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive",
typeof(bool),
typeof(VisibilityAnimation),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsActiveProperty, value);
}
static VisibilityAnimation()
{
UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement),
new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Ignore.
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return;
}
if (GetIsActive(fe))
{
HookVisibilityChanges(fe);
}
else
{
UnHookVisibilityChanges(fe);
}
}
private static void UnHookVisibilityChanges(FrameworkElement fe)
{
if (_hookedElements.Contains(fe))
{
_hookedElements.Remove(fe);
}
}
private static void HookVisibilityChanges(FrameworkElement fe)
{
_hookedElements.Add(fe, false);
}
private static object CoerceVisibility(DependencyObject d, object baseValue)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return baseValue;
}
if (CheckAndUpdateAnimationStartedFlag(fe))
{
return baseValue;
}
// If we get here, it means we have to start fade in or fade out
// animation. In any case return value of this method will be
// Visibility.Visible.
var visibility = (Visibility)baseValue;
var da = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS))
};
da.Completed += (o, e) =>
{
// This will trigger value coercion again
// but CheckAndUpdateAnimationStartedFlag() function will reture true
// this time, and animation will not be triggered.
fe.Visibility = visibility;
// NB: Small problem here. This may and probably will brake
// binding to visibility property.
};
if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
{
da.From = 1.0;
da.To = 0.0;
}
else
{
da.From = 0.0;
da.To = 1.0;
}
fe.BeginAnimation(UIElement.OpacityProperty, da);
return Visibility.Visible;
}
private static bool CheckAndUpdateAnimationStartedFlag(FrameworkElement fe)
{
var hookedElement = _hookedElements.Contains(fe);
if (!hookedElement)
{
return true; // don't need to animate unhooked elements.
}
var animationStarted = (bool)_hookedElements[fe];
_hookedElements[fe] = !animationStarted;
return animationStarted;
}
}
}
I have a user control that is nested inside a window that is acting as a shell for a dialog display. I ignore focus in the shell window, and in the hosted user control I use the FocusManager to set the initial focus to a named element (a textbox) as shown below.
This works, setting the cursor at the beginning of the named textbox; however I want all text to be selected.
The TextBoxSelectionBehavior class (below) usually does exactly that, but not in this case. Is there an easy xaml fix to get the text in the named textbox selected on initial focus?
Cheers,
Berryl
TextBox Selection Behavior
// in app startup
TextBoxSelectionBehavior.RegisterTextboxSelectionBehavior();
/// <summary>
/// Helper to select all text in the text box on entry
/// </summary>
public static class TextBoxSelectionBehavior
{
public static void RegisterTextboxSelectionBehavior()
{
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotFocusEvent, new RoutedEventHandler(OnTextBox_GotFocus));
}
private static void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (sender as TextBox);
if (tb != null)
tb.SelectAll();
}
}
The hosted UserControl
<UserControl
<DockPanel KeyboardNavigation.TabNavigation="Local"
FocusManager.FocusedElement="{Binding ElementName=tbLastName}" >
<TextBox x:Name="tbLastName" ... />
stop gap solution
Per comments with Rachel below, I ditched the FocusManger in favor of some code behind:
tbLastName.Loaded += (sender, e) => tbLastName.Focus();
Still would love a declarative approach for a simple and common chore though...
I usually use an AttachedProperty to make TextBoxes highlight their text on focus. It is used like
<TextBox local:HighlightTextOnFocus="True" />
Code for attached property
public static readonly DependencyProperty HighlightTextOnFocusProperty =
DependencyProperty.RegisterAttached("HighlightTextOnFocus",
typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));
[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
return (bool)obj.GetValue(HighlightTextOnFocusProperty);
}
public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(HighlightTextOnFocusProperty, value);
}
private static void HighlightTextOnFocusPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender != null)
{
if ((bool)e.NewValue)
{
sender.GotKeyboardFocus += OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
}
else
{
sender.GotKeyboardFocus -= OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
}
}
}
private static void OnKeyboardFocusSelectText(
object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
{
textBox.SelectAll();
}
}
private static void OnMouseLeftButtonDownSetFocus(
object sender, MouseButtonEventArgs e)
{
TextBox tb = FindAncestor<TextBox>((DependencyObject)e.OriginalSource);
if (tb == null)
return;
if (!tb.IsKeyboardFocusWithin)
{
tb.Focus();
e.Handled = true;
}
}
static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
Edit
Based on comments below, what about just getting rid of the FocusManager.FocusedElement and setting tb.Focus() and tb.SelectAll() in the Loaded event of your TextBox?
As stated above, you can add an event handler for the Loaded event to set focus and select all text:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
base.DataContext = new Person { FirstName = "Joe", LastName = "Smith" };
base.Loaded += delegate
{
this._firstNameTextBox.Focus();
this._firstNameTextBox.SelectAll();
};
}
}
I'm using the lastest version of Silverlight 2.0 within Visual Studio 2008. I have a simple Silverlight UserControl with the following code:
public partial class SilverlightControl1 : UserControl
{
public SilverlightControl1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(SilverlightControl1_Loaded);
Composite = new Composite();
}
void SilverlightControl1_Loaded(object sender, RoutedEventArgs e)
{
Composite.Width = this.Width / 2.0;
Composite.Height = this.Height / 2.0;
if (!this.LayoutRoot.Children.Contains(Composite))
this.LayoutRoot.Children.Add(Composite);
}
public Composite Composite
{
get;
set;
}
}
public class Composite : ContentControl
{
private Grid grid;
private Canvas canvas;
public Composite()
{
if (grid == null) grid = new Grid();
if (canvas == null) canvas = new Canvas();
if (!grid.Children.Contains(canvas))
grid.Children.Add(canvas);
Content = grid;
this.Loaded += new RoutedEventHandler(Composite_Loaded);
}
private Rectangle rectangle;
void Composite_Loaded(object sender, RoutedEventArgs e)
{
if (rectangle == null) rectangle = new Rectangle();
Canvas.SetTop(rectangle, 0);
Canvas.SetLeft(rectangle, 0);
rectangle.Fill = new SolidColorBrush(Color);
rectangle.Width = Width;
rectangle.Height = Height;
if (!canvas.Children.Contains(rectangle))
canvas.Children.Add(rectangle);
}
public Color Color
{
get;
set;
}
}
I then use this UserControl in a Silverlight application, the XAML of the page looking like this:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:SilverlightClassLibrary1;assembly=SilverlightClassLibrary1"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="Green">
<test:SilverlightControl1 Name="uControl1">
<test:SilverlightControl1.Composite>
<test:Composite Color="Yellow"/>
</test:SilverlightControl1.Composite>
</test:SilverlightControl1>
</Grid>
</UserControl>
My question is: what code do I have to add to the above so that by changing "Composite Color" to something other than Yellow and hitting the return button, the UserControl automatically refreshes? As the code is, the only way to refresh the UserControl is by moving the Slider bar within the VS2008 IDE which changes the percentage zoom of the Silverlight Page. A side question, although of lesser importance to the above question, is: with the code as it is above, why can't I change the "Background" color of the LayoutRoot? If I remove my UserControl it works as expected.
The solution was two-fold. Firstly to make changes in the LayoutUpdated event rather than the Loaded event and secondly to subscribe to the PropertyChangedCallback of the PropertyMetadata. Here's the complete working code:
public partial class SilverlightControl1 : UserControl
{
public SilverlightControl1()
{
InitializeComponent();
this.LayoutUpdated += new EventHandler(SilverlightControl1_LayoutUpdated);
Composite = new Composite();
}
void SilverlightControl1_LayoutUpdated(object sender, EventArgs e)
{
Composite.Width = this.Width / 2.0;
Composite.Height = this.Height / 2.0;
if (!this.LayoutRoot.Children.Contains(Composite)) this.LayoutRoot.Children.Add(Composite);
}
public Composite Composite
{
get;
set;
}
}
public class Composite : ContentControl
{
private Grid grid;
private Canvas canvas;
public Composite()
{
if (grid == null) grid = new Grid();
if (canvas == null) canvas = new Canvas();
if (!grid.Children.Contains(canvas)) grid.Children.Add(canvas);
Content = grid;
this.LayoutUpdated += new EventHandler(Composite_LayoutUpdated);
}
void Composite_LayoutUpdated(object sender, EventArgs e)
{
if (rectangle == null) rectangle = new Rectangle();
Canvas.SetTop(rectangle, 0);
Canvas.SetLeft(rectangle, 0);
rectangle.Fill = new SolidColorBrush(Color);
rectangle.Width = Width;
rectangle.Height = Height;
if (!canvas.Children.Contains(rectangle)) canvas.Children.Add(rectangle);
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Color), typeof(Composite), new PropertyMetadata(Colors.Red, new PropertyChangedCallback(OnColorPropertyChanged)));
private static void OnColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Composite comp = (Composite)d;
comp.InvalidateArrange();
}
private Rectangle rectangle;
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
}
I think you need to make Composite into a Dependency Proprety. Probably will want to do the same thing for Color on Composite so that you'll be able to bind it.