I've a simple Grid defined this way:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="24" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
In every cell of the grid (except the upperleft cell) I add a Grid or Canvas. Into these containers I add several different objects. Some of these controls can change there viewing size because of zooming in or out and scrolling.
The original code is not my own, but I made a little test program to simulate the situation:
<Grid Grid.Column="1" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid x:Name="Frame" Grid.Row="0">
<Canvas Width="200" Height="300" Background="Green" >
<Canvas x:Name="Page" Width="200" Height="300" Background="Bisque" Margin="0 -20 0 0">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="{Binding ElementName=Zoom, Path=Value}"
ScaleY="{Binding ElementName=Zoom, Path=Value}"
CenterX="100" CenterY="150" />
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>
<Slider x:Name="Zoom" Grid.Row="1" HorizontalAlignment="Right" Width="200"
Minimum="0.1" Maximum="2" Value="1"
TickPlacement="BottomRight" TickFrequency="0.1" IsSnapToTickEnabled="True" />
The Page is too big and goes out of range, especially when I zoom in.
I try to add a Clip, but I do not know how to set the value dynamically.
<Grid x:Name="Frame" Grid.Row="0">
<Grid.Clip>
<!-- I want to bind to the actual size of the cell -->
<RectangleGeometry Rect="0 0 480 266" />
</Grid.Clip>
<Canvas Width="200" Height="300" Background="Green" >
....
Moreover, how can I get the actual size and position of the rendered canvas. I inserted Zoom_ValueChanged to read out the values after zooming, but Width & Height are still 200 or 300, ActualWidth & ActualHeight are both zero.
Thanks in advance.
Em1, make sure you are checking the ActualWidth and ActualHeight of the canvas after your content has finished loading (i.e. after the Loaded event has been raised).
Also, one way to get the size of a canvas taking into account all of the scale transformations that have been applied to it is to walk up the visual tree and apply all scale transforms to the ActualWidth and ActualHeight of a control:
public static Size GetActualSize(FrameworkElement control)
{
Size startSize = new Size(control.ActualWidth, control.ActualHeight);
// go up parent tree until reaching root
var parent = LogicalTreeHelper.GetParent(control);
while(parent != null && parent as FrameworkElement != null && parent.GetType() != typeof(Window))
{
// try to find a scale transform
FrameworkElement fp = parent as FrameworkElement;
ScaleTransform scale = FindScaleTransform(fp.RenderTransform);
if(scale != null)
{
startSize.Width *= scale.ScaleX;
startSize.Height *= scale.ScaleY;
}
parent = LogicalTreeHelper.GetParent(parent);
}
// return new size
return startSize;
}
public static ScaleTransform FindScaleTransform(Transform hayStack)
{
if(hayStack is ScaleTransform)
{
return (ScaleTransform) hayStack;
}
if(hayStack is TransformGroup)
{
TransformGroup group = hayStack as TransformGroup;
foreach (var child in group.Children)
{
if(child is ScaleTransform)
{
return (ScaleTransform) child;
}
}
}
return null;
}
To get the position of a control, you need to find its transformation relative to the containing window. Here's how:
public static Point TransformToWindow(Visual control)
{
var hwndSource = PresentationSource.FromVisual(control) as HwndSource;
if (hwndSource == null)
return new Point(-1, -1);
Visual root = hwndSource.RootVisual; // Translate the point from the visual to the root.
GeneralTransform transformToRoot = control.TransformToAncestor(root);
return transformToRoot.Transform(new Point(0, 0));
}
Related
First of all I am working with MVVM / WPF / .Net Framework 4.6.1
I have a ListView configured with ItemsPanelTemplate in horizontal orientation that displays items from a DataTemplate. This setup allows me to fit as many items inside the Width of the ListView (the witdth size is the same from the Window), and behaves responsively when I resize the window.
So far everything is fine, now I just want to Identify what items are positioned on the first row, including when the window get resized and items inside the first row increase or decrease.
I merely want to accomplish this behavior because I would like to apply a different template style for those items (let's say a I bigger image or different text color).
Here below the XAML definition for the ListView:
<ListView x:Name="lv"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BTW: I already did a work around where I am getting the Index from each ListViewItem and calculating against the Width of the Grid inside the DataTemplate that is a fixed value of 180, but unfortunately it did not work as I expected since I had to use a DependencyProperty to bind the ActualWidth of the of the ListView to my ViewModel and did not responded very well when I resized the window.
I know I am looking for a very particular behavior, but if anyone has any suggestions about how to deal with this I would really appreciate. Any thoughts are welcome even if you think I should be using a different control, please detail.
Thanks in advance!
You shouldn't handle the layout in any view model. If you didn't extend ListView consider to use an attached behavior (raw example):
ListBox.cs
public class ListBox : DependencyObject
{
#region IsAlternateFirstRowTemplateEnabled attached property
public static readonly DependencyProperty IsAlternateFirstRowTemplateEnabledProperty = DependencyProperty.RegisterAttached(
"IsAlternateFirstRowTemplateEnabled",
typeof(bool), typeof(ListView),
new PropertyMetadata(default(bool), ListBox.OnIsEnabledChanged));
public static void SetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty, value);
public static bool GetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement) => (bool)attachingElement.GetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty);
#endregion
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is System.Windows.Controls.ListBox listBox))
{
return;
}
if ((bool)e.NewValue)
{
listBox.Loaded += ListBox.Initialize;
}
else
{
listBox.SizeChanged -= ListBox.OnListBoxSizeChanged;
}
}
private static void Initialize(object sender, RoutedEventArgs e)
{
var listBox = sender as System.Windows.Controls.ListBox;
listBox.Loaded -= ListBox.Initialize;
// Check if items panel is WrapPanel
if (!listBox.TryFindVisualChildElement(out WrapPanel panel))
{
return;
}
listBox.SizeChanged += ListBox.OnListBoxSizeChanged;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void OnListBoxSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!e.WidthChanged)
{
return;
}
var listBox = sender as System.Windows.Controls.ListBox;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void ApplyFirstRowDataTemplate(System.Windows.Controls.ListBox listBox)
{
double calculatedFirstRowWidth = 0;
var firstRowDataTemplate = listBox.Resources["FirstRowDataTemplate"] as DataTemplate;
foreach (FrameworkElement itemContainer in listBox.ItemContainerGenerator.Items
.Select(listBox.ItemContainerGenerator.ContainerFromItem).Cast<FrameworkElement>())
{
calculatedFirstRowWidth += itemContainer.ActualWidth;
if (itemContainer.TryFindVisualChildElement(out ContentPresenter contentPresenter))
{
if (calculatedFirstRowWidth > listBox.ActualWidth - listBox.Padding.Right - listBox.Padding.Left)
{
if (contentPresenter.ContentTemplate == firstRowDataTemplate)
{
// Restore the default template of previous first row items
contentPresenter.ContentTemplate = listBox.ItemTemplate;
continue;
}
break;
}
contentPresenter.ContentTemplate = firstRowDataTemplate;
}
}
}
}
Helper Extension Method
/// <summary>
/// Traverses the visual tree towards the leafs until an element with a matching element type is found.
/// </summary>
/// <typeparam name="TChild">The type the visual child must match.</typeparam>
/// <param name="parent"></param>
/// <param name="resultElement"></param>
/// <returns></returns>
public static bool TryFindVisualChildElement<TChild>(this DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (childElement.TryFindVisualChildElement(out resultElement))
{
return true;
}
}
return false;
}
Usage
<ListView x:Name="lv"
ListBox.IsAlternateFirstRowTemplateEnabled="True"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<DataTemplate x:Key="FirstRowDataTemplate">
<!-- Draw a red border around first row items -->
<Border BorderThickness="2" BorderBrush="Red">
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</Border>
</DataTemplate>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Remarks
If the visual tree itself will not change for the first row, consider to add a second attached property to the ListBox class (e.g., IsFirstRowItem) which you would set on the ListBoxItems. You can then use a DataTrigger to modify the control properties to change the appearance. This will very likely increase the performance too.
In my WPF app, I have four separate quadrants, each with it's own grid and data. The four grids are separated by GridSplitters. The GridSplitters allow the user to resize each box by selecting either a horizontal or vertical splitter.
I am trying to allow the user to resize the grids by selecting the center point (circled in red).
I expected to have a four-way mouse pointer that could be used to drag up, down, left, and right. But, I only have the option to move windows up and down... or left and right.
What I've tried:
<Grid> <!-- Main Grid that holds A, B, C, and D -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="gridA" Grid.Column="0" Grid.Row="0"/>
<GridSplitter Grid.Column="0" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridC" Grid.Column="2" Grid.Row="0"/>
<GridSplitter Grid.Column="3" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridB" Grid.Column="0" Grid.Row="2"/>
<GridSplitter Grid.Column="1" Grid.Row="0" Width="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridD" Grid.Column="2" Grid.Row="2"/>
<GridSplitter Grid.Column="1" Grid.Row="2" Width="5" HorizontalAlignment="Stretch"/>
</Grid>
Let me begin by changing your XAML a little bit, since right now we have four distinct GridSplitters, but two is enough:
<Grid Name="SplitGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="GridA" Grid.Column="0" Grid.Row="0" Background="Red" />
<Grid x:Name="GridC" Grid.Column="2" Grid.Row="0" Background="Orange" />
<Grid x:Name="GridB" Grid.Column="0" Grid.Row="2" Background="Green" />
<Grid x:Name="GridD" Grid.Column="2" Grid.Row="2" Background="Yellow" />
<GridSplitter x:Name="VerticalSplitter"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="5"
Background="Black" />
<GridSplitter x:Name="HorizontalSplitter"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="5"
HorizontalAlignment="Stretch"
Background="Black" />
</Grid>
What is more important about this markup is that we now have an intersection point between two splitters:
In order to drag two splitters at a time, we need to know when should we. For that purpose, let's define a Boolean flag:
public partial class View : Window
{
private bool _mouseIsDownOnBothSplitters;
}
We need to update the flag whenever the user clicks on either of the splitters (note that Preview events are used - GridSplitter implementation marks Mouse events as Handled):
void UpdateMouseStatusOnSplittersHandler(object sender, MouseButtonEventArgs e)
{
UpdateMouseStatusOnSplitters(e);
}
VerticalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;
VerticalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;
The UpdateMouseStatusOnSplitters is the core method here. WPF does not provide multiple layer hit testing "out of the box", so we'll have to do a custom one:
private void UpdateMouseStatusOnSplitters(MouseButtonEventArgs e)
{
bool horizontalSplitterWasHit = false;
bool verticalSplitterWasHit = false;
HitTestResultBehavior HitTestAllElements(HitTestResult hitTestResult)
{
return HitTestResultBehavior.Continue;
}
//We determine whether we hit our splitters in a filter function because only it tests the visual tree
//HitTestAllElements apparently only tests the logical tree
HitTestFilterBehavior IgnoreNonGridSplitters(DependencyObject hitObject)
{
if (hitObject == SplitGrid)
{
return HitTestFilterBehavior.Continue;
}
if (hitObject is GridSplitter)
{
if (hitObject == HorizontalSplitter)
{
horizontalSplitterWasHit = true;
return HitTestFilterBehavior.ContinueSkipChildren;
}
if (hitObject == VerticalSplitter)
{
verticalSplitterWasHit = true;
return HitTestFilterBehavior.ContinueSkipChildren;
}
}
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
VisualTreeHelper.HitTest(SplitGrid, IgnoreNonGridSplitters, HitTestAllElements, new PointHitTestParameters(e.GetPosition(SplitGrid)));
_mouseIsDownOnBothSplitters = horizontalSplitterWasHit && verticalSplitterWasHit;
}
Now we can implement the concurrent dragging. This will be done via a handler for DragDelta. However, there are a few caveats:
We only need to implement the handler for the splitter that is on top (in my case that'll be the HorizontalSplitter)
The Change value in DragDeltaEventArgs is bugged, the _lastHorizontalSplitterHorizontalDragChange is a workaround
To actually "drag" the other splitter, we'll have to change the dimensions of our Column/RowDefinitions. In order to avoid weird clipping behavior (the splitter dragging the column/row with it), we'll have to use the size of it in pixels as the the size of it in stars
So, with that out of the way, here's the relevant handler:
private void HorizontalSplitter_DragDelta(object sender, DragDeltaEventArgs e)
{
if (_mouseIsDownOnBothSplitters)
{
var firstColumn = SplitGrid.ColumnDefinitions[0];
var thirdColumn = SplitGrid.ColumnDefinitions[2];
var horizontalOffset = e.HorizontalChange - _lastHorizontalSplitterHorizontalDragChange;
var maximumColumnWidth = firstColumn.ActualWidth + thirdColumn.ActualWidth;
var newProposedFirstColumnWidth = firstColumn.ActualWidth + horizontalOffset;
var newProposedThirdColumnWidth = thirdColumn.ActualWidth - horizontalOffset;
var newActualFirstColumnWidth = newProposedFirstColumnWidth < 0 ? 0 : newProposedFirstColumnWidth;
var newActualThirdColumnWidth = newProposedThirdColumnWidth < 0 ? 0 : newProposedThirdColumnWidth;
firstColumn.Width = new GridLength(newActualFirstColumnWidth, GridUnitType.Star);
thirdColumn.Width = new GridLength(newActualThirdColumnWidth, GridUnitType.Star);
_lastHorizontalSplitterHorizontalDragChange = e.HorizontalChange;
}
}
Now, this is almost a full solution. It, however, suffers from the fact that even if you move your mouse horizontally outside of the grid, the VerticalSplitter still moves with it, which is inconsistent with the default behavior. In order to counteract this, let's add this check to the handler's code:
if (_mouseIsDownOnBothSplitters)
{
var mousePositionRelativeToGrid = Mouse.GetPosition(SplitGrid);
if (mousePositionRelativeToGrid.X > 0 && mousePositionRelativeToGrid.X < SplitGrid.ActualWidth)
{
//The rest of the handler's code
}
}
Finally, we need to reset our _lastHorizontalSplitterHorizontalDragChange to zero when the dragging is over:
HorizontalSplitter.DragCompleted += (o, e) => _lastHorizontalSplitterHorizontalDragChange = 0;
I hope it is not too daring of me to leave the implementation of the cursor's image change to you.
I have a UserControl:
<UserControl d:DesignHeight="100" d:DesignWidth="200" ...>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Ellipse Name="leftEllipse" Grid.Column="0" Width="50" Height="50" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="Red" />
<Ellipse Name="rightEllipse" Grid.Column="1" Width="50" Height="50" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="Green" />
</Grid>
</UserControl>
Here is my MainWindow:
<Window ...>
<Canvas Name="canvas1">
<my:MyUserControl x:Name="myUserControl1" Width="200" Height="100" Canvas.Top="100" Canvas.Left="100" />
</Canvas>
</Window>
I know how to get the position of myUserControl1 :
double x = Canvas.GetLeft(myUserControl1);
But can anyone tell me how to get the position of myUserControl1.leftEllipse?
And when myUserControl1 apply a RotateTransform, the myUserControl1.leftEllipse's position will changed, won't it?
Without making the generated leftEllipse field public, you could add a method to the UserControl that returns a transform object from the Ellipse's coordinates to that of an ancestor element, e.g.
public GeneralTransform LeftEllipseTransform(UIElement e)
{
return leftEllipse.TransformToAncestor(e);
}
You may then call it in your MainWindow like this:
var p = myUserControl1.LeftEllipseTransform(this).Transform(new Point());
Instead of TransformToAncestor (or TransformToVisual) you may also use TranslatePoint.
public Point GetLeftEllipsePosition(Point p, UIElement e)
{
return leftEllipse.TranslatePoint(p, e);
}
In MainWindow:
var p = myUserControl1.GetLeftEllipsePosition(new Point(), this);
Or for the center of the Ellipse (instead of its upper left corner):
var p = myUserControl1.GetLeftEllipsePosition(new Point(25, 25), this);
I have a ScrollViewer with HorizontalScrollBarVisibility set to "Auto" that contains a TextBox. The problem is that when a user enters text, the TextBox keeps growing in order to show the entire content. What do I need to change, so that the TextBox only grabs the available width (but is not smaller than a given minimal width)?
The horizontal scroll-bar should only appear if the available horizontal space is not sufficient for the given minimal width.
The TextBox should only grow if there is more horizontal space available.
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*" MinWidth="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="test:"/>
<TextBox Grid.Column="1"/>
</Grid>
</ScrollViewer>
The horizontal scrollbar appears even though the MinWidth constrain is fulfilled:
This seems to be a common problem but I haven't found a satisfying solution on the net.
Here is my solution:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*" MinWidth="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="test:"/>
<local:TextBoxDecorator Grid.Column="1">
<TextBox Text="content content content content content content"/>
</local:TextBoxDecorator>
</Grid>
</ScrollViewer>
c#
public class TextBoxDecorator : Decorator {
// properties
public override UIElement Child {
get {
return base.Child;
}
set {
var oldValue = base.Child;
if (oldValue != null) {
var binding = BindingOperations.GetBinding(oldValue, FrameworkElement.WidthProperty);
if ((binding != null) && (binding.Source == this))
BindingOperations.ClearBinding(oldValue, FrameworkElement.WidthProperty);
}
base.Child = value;
if ((value != null) &&
BindingOperations.GetBinding(value, FrameworkElement.WidthProperty) == null)
BindingOperations.SetBinding(
value,
FrameworkElement.WidthProperty,
new Binding() {
Source = this,
Path = new PropertyPath(FrameworkElement.ActualWidthProperty),
Mode = BindingMode.OneWay
});
}
}
// methods
protected override Size MeasureOverride(Size constraint) {
Size result = base.MeasureOverride(constraint);
if (double.IsInfinity(constraint.Width))
result.Width = (Child as FrameworkElement)?.MinWidth ?? 0.0;
return result;
}
}
Let me know if this was helpful or if you have any feedback.
I've been working a bit with an image editor in Silverlight for the past week or so. It's my first encounter with it and I still haven't fully gotten my head around data bindings and datacontext or mvvm. I have a rotation method and I want to be able to pass the angle value from a text box on my MainPage.xaml to the method.I have an initial value set of 90 and my function rotates the image 90 degrees when I click it. The textbox is empty at runtime and also is clearly not updating my rotation angle.
MainPage.xaml
<Grid DataContext="{Binding Path=Project}" Height="70" Name="grid1" Width="200">
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="84*" />
<ColumnDefinition Width="57*" />
<ColumnDefinition Width="59*" />
</Grid.ColumnDefinitions>
<Button Command="{Binding Path=RotateCWElementCommand}"
Height="30" Name="btnRotCW" Width="30" Grid.Column="2" Margin="15,0,14,5" Grid.Row="1">
<Image Source="../Assets/Images/Icons/object-rotate-right.png" Grid.Column="1"
Grid.Row="4" Height="20" HorizontalAlignment="Left" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="20" />
</Button>
<TextBlock FontWeight="Bold" HorizontalAlignment="Left" Margin="10,10,0,8" Text="Rotate" VerticalAlignment="Center" />
<TextBlock FontWeight="Bold" HorizontalAlignment="Left" Margin="34,9,0,10" Text="Angle:" VerticalAlignment="Center" Grid.Row="1" />
<TextBox Text="{Binding Path=RotateElement.Angle, Mode=TwoWay}" Height="24" Name="textBox1" Width="52" Grid.Column="1" Margin="0,6,5,5" Grid.Row="1" />
</Grid>
(Relevant code from)
Project.cs-
namespace ImageEditor.Client.BLL
{
public class Project : INotifyPropertyChanged
{
#region Properties
private RotateTransform rotateElement;
public RotateTransform RotateElement
{
get { return rotateElement; }
set
{
rotateElement = value;
NotifyPropertyChanged("RotateElement");
}
}
#endregion
#region Methods
private void RotateCWElement(object param)
{
FrameworkElement element = this.SelectedElement;
RotateTransform RotateElement = new RotateTransform();
RotateElement.Angle = 90;
RotateElement.CenterX = element.ActualWidth * 0.5;
RotateElement.CenterY = element.ActualHeight * 0.5;
element.RenderTransform = RotateElement;
}
What am I doing wrong here? Is it my datacontext or my binding path that is the problem? What should they be? Formatting is a little off sorry
the UI does not know that a property in your object has changed, since you only notify when your object changes, but not the properties in it:
private void RotateCWElement(object param)
{
FrameworkElement element = this.SelectedElement;
if (this.RotateElement == null) this.RotateElement = new RotateTransform();
RotateElement.Angle = 90;
RotateElement.CenterX = element.ActualWidth * 0.5;
RotateElement.CenterY = element.ActualHeight * 0.5;
element.RenderTransform = RotateElement;
//tell the UI that this property has changed
NotifyPropertyChanged("RotateElement");
}