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.
Related
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.
I have put together a simple WPF application to demonstrate the issue I am having. My XAML is below:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="427" Width="467" Loaded="MainWindow_OnLoaded">
<Grid>
<ScrollViewer Name="MyScrollViewer" CanContentScroll="True">
<Image Name="MyImage" HorizontalAlignment="Left" VerticalAlignment="Top" MouseWheel="UIElement_OnMouseWheel" MouseDown="MyImage_OnMouseDown" MouseUp="MyImage_OnMouseUp"/>
</ScrollViewer>
</Grid>
</Window>
The code-behind is below:
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void UIElement_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var matrix = MyImage.RenderTransform.Value;
if (e.Delta > 0)
{
matrix.ScaleAt(1.5, 1.5, e.GetPosition(this).X, e.GetPosition(this).Y);
}
else
{
matrix.ScaleAt(1.0 / 1.5, 1.0 / 1.5, e.GetPosition(this).X, e.GetPosition(this).Y);
}
MyImage.RenderTransform = new MatrixTransform(matrix);
}
private WriteableBitmap writeableBitmap;
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
var image = new WriteableBitmap(new BitmapImage(new Uri(#"C:\myImage.png", UriKind.Absolute)));
MyImage.Width = image.Width;
MyImage.Height = image.Height;
image = BitmapFactory.ConvertToPbgra32Format(image);
writeableBitmap = image;
MyImage.Source = image;
}
private Point downPoint;
private Point upPoint;
private void MyImage_OnMouseDown(object sender, MouseButtonEventArgs e)
{
downPoint = e.GetPosition(MyImage);
}
private void MyImage_OnMouseUp(object sender, MouseButtonEventArgs e)
{
upPoint = e.GetPosition(MyImage);
writeableBitmap.DrawRectangle(Convert.ToInt32(downPoint.X), Convert.ToInt32(downPoint.Y), Convert.ToInt32(upPoint.X), Convert.ToInt32(upPoint.Y), Colors.Red);
MyImage.Source = writeableBitmap;
}
}
}
I have added WriteableBitmapEx using Nuget. If you run this, and replace myImage.png with the location of an actual image on your computer, you will find an application that looks something like this:
You can draw a box on the image by clicking on the top-left of where you want the box to go and dragging to the bottom right of where you want the box to go, you get a red rectangle. You can even zoom in with the middle-mouse, and draw a rectangle up close for more precision, this works as expected.
The problem is that, when you scroll in with the middle mouse, the scroll bars don't re-adjust, which is a requirement of the program I am creating. My question is how do I force the scrollbars on the scrollviewer to re-adjust when the image is zoomed in?
I am convinced it has something to do with the RenderTransform property of the ScrollViewer, and that I need to update it at the same time I update the RenderTransform property of the image (on UIElement_OnMouseWheel) but I am unsure of exactly how to go about this.
You should be using LayoutTransform instead of RenderTransform on your Image.
RenderTransform happens after layout completes and is visual only. LayoutTransform is done before the layout pass and so can notify the ScrollViewer of the new size.
See here for more info: http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.layouttransform.aspx
For pure scrolling I'd rather use a ScaleTransform, it should adjust the scrollbars accordingly.
You can try below code if it fixes your issue.
private double _zoomValue = 1.0;
private void UIElement_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
_zoomValue += 0.1;
}
else
{
_zoomValue -= 0.1;
}
ScaleTransform scale = new ScaleTransform(_zoomValue, _zoomValue);
MyImage.LayoutTransform = scale;
e.Handled = true;
}
Let's assume you have a Canvas_Main inside a ViewBox_CanvasMain, which in turn inside a ScrollViewer_CanvasMain. You want to zoom in by turning the mouse wheel, and the ScrollViewer will adjust the offset automatically so the feature (pointed by the mouse in the Canvas_Main) stays during the zoom in/out. It is complex but here is the code called by the mouse wheel eventhandler:
private void MouseWheelZoom(MouseWheelEventArgs e)
{
if(Canvas_Main.IsMouseOver)
{
Point mouseAtImage = e.GetPosition(Canvas_Main); // ScrollViewer_CanvasMain.TranslatePoint(middleOfScrollViewer, Canvas_Main);
Point mouseAtScrollViewer = e.GetPosition(ScrollViewer_CanvasMain);
ScaleTransform st = ViewBox_CanvasMain.LayoutTransform as ScaleTransform;
if (st == null)
{
st = new ScaleTransform();
ViewBox_CanvasMain.LayoutTransform = st;
}
if (e.Delta > 0)
{
st.ScaleX = st.ScaleY = st.ScaleX * 1.25;
if (st.ScaleX > 64) st.ScaleX = st.ScaleY = 64;
}
else
{
st.ScaleX = st.ScaleY = st.ScaleX / 1.25;
if (st.ScaleX < 1) st.ScaleX = st.ScaleY = 1;
}
#region [this step is critical for offset]
ScrollViewer_CanvasMain.ScrollToHorizontalOffset(0);
ScrollViewer_CanvasMain.ScrollToVerticalOffset(0);
this.UpdateLayout();
#endregion
Vector offset = Canvas_Main.TranslatePoint(mouseAtImage, ScrollViewer_CanvasMain) - mouseAtScrollViewer; // (Vector)middleOfScrollViewer;
ScrollViewer_CanvasMain.ScrollToHorizontalOffset(offset.X);
ScrollViewer_CanvasMain.ScrollToVerticalOffset(offset.Y);
this.UpdateLayout();
e.Handled = true;
}
}
I've got a custom horizontal ListView with custom ScrollViewer inside it's template (created with Blend). I want it to scroll horizontally when using mouse scrolling wheel.
How can I do that?
This should be done with a Behavior for greater reusability. Also, the logic from ZSH is redundant and could be simplified. Here's my code:
/// <summary>
/// Allows an <see cref="ItemsControl"/> to scroll horizontally by listening to the
/// <see cref="PreviewMouseWheel"/> event of its internal <see cref="ScrollViewer"/>.
/// </summary>
public class HorizontalScrollBehavior : Behavior<ItemsControl>
{
/// <summary>
/// A reference to the internal ScrollViewer.
/// </summary>
private ScrollViewer ScrollViewer { get; set; }
/// <summary>
/// By default, scrolling down on the wheel translates to right, and up to left.
/// Set this to true to invert that translation.
/// </summary>
public bool IsInverted { get; set; }
/// <summary>
/// The ScrollViewer is not available in the visual tree until the control is loaded.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
AssociatedObject.Loaded -= OnLoaded;
ScrollViewer = VisualTreeHelpers.FindVisualChild<ScrollViewer>(AssociatedObject);
if (ScrollViewer != null)
{
ScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (ScrollViewer != null)
{
ScrollViewer.PreviewMouseWheel -= OnPreviewMouseWheel;
}
}
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var newOffset = IsInverted ?
ScrollViewer.HorizontalOffset + e.Delta :
ScrollViewer.HorizontalOffset - e.Delta;
ScrollViewer.ScrollToHorizontalOffset(newOffset);
}
}
You'll need to add the following references:
System.Windows, System.Windows.Controls, System.Windows.Input, and you may need to get the Blend SDK NuGet package, and find and reference the System.Windows.Interactivity DLL in the Assemblies Extensions section.
Use this for VisualTreeHelpers:
public class VisualTreeHelpers
{
/// <summary>
/// Return the first visual child of element by type.
/// </summary>
/// <typeparam name="T">The type of the Child</typeparam>
/// <param name="obj">The parent Element</param>
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
return (T)child;
else
{
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
}
Reference: https://codereview.stackexchange.com/questions/44760/is-there-a-better-way-to-get-a-child
Note that it is NOT the same as VisualTreeHelper in Windows.System.Media.
Here's how to use it in XAML:
<ListBox>
<i:Interaction.Behaviors>
<behaviors:HorizontalScrollBehavior />
</i:Interaction.Behaviors>
</ListBox>
Where the i namespace is declared as xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and
behaviors is declared as
xmlns:behaviors="clr-namespace:MyNamespace"
where MyNamespace is the namespace that contains the HorizontalScrollBehavior class.
if you implement IScrollInfo you can override the MouseWheelUp to do MouseWheelLeft
and down\right the in same way
edit (much more simple):
add to your ScrollViewer PreviewMouseWheel
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta < 0) // wheel down
{
if (myScrollViewer.HorizontalOffset + e.Delta > 0)
{
myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);
}
else
{
myScrollViewer.ScrollToLeftEnd();
}
}
else //wheel up
{
if (myScrollViewer.ExtentWidth > myScrollViewer.HorizontalOffset + e.Delta)
{
myScrollViewer.ScrollToHorizontalOffset(myScrollViewer.HorizontalOffset + e.Delta);
}
else
{
myScrollViewer.ScrollToRightEnd();
}
}
}
xaml:
<ScrollViewer x:Name="myScrollViewer" HorizontalScrollBarVisibility="Visible" Mouse.PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
I was kinda looking for the most simple way to make any ScrollViewer scroll left-right instead of up-down. So here is the simplest combination of the other answers.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
and:
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (ScrollViewer)sender;
if (e.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
e.Handled = true;
}
Xaml Code:
<ScrollViewer HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
</ScrollViewer>
C# Code
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var scrollViewer = (ScrollViewer)sender;
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - e.Delta);
e.Handled = true;
}
}
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.