LayoutTransform animation on ScaleX and ScaleY is breaking binding with them - wpf

I have a custom class say 'MyCanvas' derived from wpf Canvas class. MyCanvas has a Dependency Property 'Scale' which specify the scale transform for the canvas. Now when the value of Scale changes I want to animate the transform from old value to new value. for that I am using LayoutTransform.BeginAnimation(...) method.
Code:
//This represents the time it will take to zoom
Duration zoomDuration = new Duration(TimeSpan.Parse("0:0:0.3"));
//Set up animation for zooming
DoubleAnimationUsingKeyFrames scaleAnimation = new DoubleAnimationUsingKeyFrames();
scaleAnimation.Duration = zoomDuration;
scaleAnimation.KeyFrames = GetAnimationSplines(newScale);
scaleAnimation.Completed += new EventHandler(
(sender, e) => Scale = newScale);
// Start the scale (zoom) animations
LayoutTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation);
LayoutTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation);
XMAL:
<Grid>
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:MyCanvas Scale="{Binding Scale, Mode=TwoWay}">
<local:MyCanvas.LayoutTransform UseLayoutRounding="True">
<ScaleTransform ScaleX="{Binding Scale, Mode=TwoWay}"
ScaleY="{Binding Scale, Mode=TwoWay}"/>
</local:MyCanvas.LayoutTransform>
</local:MyCanvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
But after executing this code, the binding of ScaleX and ScaleY with Scale ( a property in viewmodel) is breaking i.e. changing value of Scale does not change scale on canvas.
Error Message (using Snoop):
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Scale; DataItem=null; target element is 'ScaleTransform' (HashCode=796423); target property is 'ScaleX' (type 'Double')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Scale; DataItem=null; target element is 'ScaleTransform' (HashCode=796423); target property is 'ScaleY' (type 'Double')
Please let me know if any one has solution for this. Thanks

This is not a solution to above problem but a working sample WPF Application to reproduce the above mention issue.
XAML:
<Window x:Class="TestWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=zoomer, Path=Value}" ScaleY="{Binding ElementName=zoomer, Path=Value}" x:Name="scaleTx" />
</Grid.LayoutTransform>
<Border Background="Aqua" BorderThickness="3" BorderBrush="Blue" Height="50" Width="50" />
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Slider x:Name="zoomer" Width="200" Value="1" Minimum="0.1" Maximum="3" TickFrequency="0.1" IsSnapToTickEnabled="True" Margin="3"/>
<Button Content="Animate" Click="Button_Click" Margin="3"/>
</StackPanel>
</Grid>
Code:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//This represents the time it will take to zoom
Duration zoomDuration = new Duration(TimeSpan.Parse("0:0:0.5"));
//Set up animation for zooming
DoubleAnimationUsingKeyFrames scaleAnimation = new DoubleAnimationUsingKeyFrames();
scaleAnimation.Duration = zoomDuration;
scaleAnimation.KeyFrames = GetAnimationSplines(zoomer.Maximum);
scaleTx.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation);
scaleTx.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation);
}
/// <summary>
/// This creates a spline for zooming with non-linear acceleration patterns.
/// </summary>
/// <param name="moveTo"></param>
/// <returns></returns>
protected DoubleKeyFrameCollection GetAnimationSplines(double moveTo)
{
DoubleKeyFrameCollection returnCollection = new DoubleKeyFrameCollection();
returnCollection.Add(new LinearDoubleKeyFrame(moveTo, KeyTime.FromPercent(1.0)));
return returnCollection;
}
}
The shape in the center of window will scale when slide is moved. Once you click animate button to apply animation, the zooming will animate but after that slider stops working.

Well, Today I was trying some thing and suddenly the issue got resolved. I did not understand how it start working. I made following changes to the code, which I tried earlier too.
scaleAnimation.FillBehavior = FillBehavior.Stop;
Can anyone explain this to me. Thanks.

I can corroborate that I've had this happen too, but I haven't yet found a clean solution. I had a case just like you describe in one of your answers - regarding the slider change not affecting the bound object after the animation has run (and yes, I had the FillBehavior set to Stop and/or run BeginAnimation(someProperty, null) to "release" the animation's "hold" on the property. To illustrate that I could still edit the property after animating (but not with the slider that was the source of the binding), I setup a button that would set the property to a specific value. This button did in fact work perfectly to change that property after the animation. It seems that in some cases WPF's binding may break as the result of certain animations.
A not-so-clean workaround might be to re-establish the binding in code in the animation's Completed handler.

Related

Custom attached property not found on resource when embedded in user control

I have a resource that need to be a different color depending on where it is used, so I use this attached property:
public static class AssetProperties
{
public static Brush GetFillBrush(DependencyObject obj)
{
return (Brush)obj.GetValue(FillBrushProperty);
}
public static void SetFillBrush(DependencyObject obj, Brush value)
{
obj.SetValue(FillBrushProperty, value);
}
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.RegisterAttached("FillBrush",
typeof(Brush),
typeof(AssetProperties),
new FrameworkPropertyMetadata(new BrushConverter().ConvertFrom("#FFE41300"), FrameworkPropertyMetadataOptions.Inherits));
}
We define the symbol and use it something like this in a window or user control (this is of course a lot simplified, the resource is for example defined in a separate file) :
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10"
Fill="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />
</ResourceDictionary>
</Grid.Resources>
<ContentControl Content="{StaticResource SomeColorfulSymbol}" main:AssetProperties.FillBrush="Blue"/>
</Grid>
This works as intended, a nice blue rectangle appears. Without setting the attached property, the rectangle is the default red of the FillBrush attached property.
The problem is when we try to use the symbol inside a custom user control defined like this:
OuterControl.xaml:
<UserControl x:Class="AttachedPropertyResourceTest.OuterControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="Some title"/>
<ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"/>
</StackPanel>
</Grid>
</UserControl>
OuterControl.xaml.cs:
[ContentProperty("InnerContent")]
public partial class OuterControl
{
public FrameworkElement InnerContent
{
get { return (FrameworkElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(OuterControl), new FrameworkPropertyMetadata(null));
public OuterControl()
{
InitializeComponent();
}
}
Now if I wrap the ContentControl in the above snippet like this instead:
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
it looks good in the VS designer, a title plus a rectangle that is the default red of FillBrush. In runtime however we only get the title. The rectangle gets no color (UnsetValue) and we get this binding error:
System.Windows.Data Error: 40 : BindingExpression path error:
'(main:AssetProperties.FillBrush)' property not found on 'object'
''Rectangle' (Name='')'.
BindingExpression:Path=(main:AssetProperties.FillBrush);
DataItem='Rectangle' (Name=''); target element is 'Rectangle'
(Name=''); target property is 'Fill' (type 'Brush')
If I add an invisible instance of the symbol before the wrapped one, it works again, i.e., a red rectangle appears:
<ContentControl Content="{StaticResource SomeColorfulSymbol}" Visibility="Collapsed"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
One problem is that the attached property is not registered, when I put a breakpoint on the RegisterAttached method it is not called without the extra invisible ContentControl. This is however only a part of the problem, for example forcing the registration like this does not work:
<StackPanel>
<TextBlock Text="I'm red!" Background="{Binding (main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}"/>
<main:OuterControl>
<ContentControl Content="{StaticResource SomeColorfulSymbol}"/>
</main:OuterControl>
</StackPanel>
The text "I'm red" is actually red and the attached property is registered, but we get the exact same binding error.
I also tried without the ContentProperty["InnerContent"], setting the InnerContent attribute explicitly in xaml, with the same result.
Could someone shed some light on this?
Maybe using a control template instead of OuterControl wouldn't have this problem (?), but there is a lot of behavior associated with OuterControl and I would prefer this approach.
To prevent the following issue, try specifying the path property explicitly like:{Binding Path=(main:....}
<Rectangle x:Key="SomeColorfulSymbol" x:Shared="False" Width="10" Height="10" Fill="{Binding Path=(main:AssetProperties.FillBrush), RelativeSource={RelativeSource Self}}" />

Binding fails when used inside MenuItem.Icon

I want to offer a context menu with an item that has a color swatch in the space where "icons" are normally placed for such menu items, i.e. the space corresponding to MenuItem.Icon.
But the color swatch is dynamic--a Brush property on the UserControl that (in this crafted example) changes to a random color in response to the ContextMenuOpening event--and my attempt at binding to it is failing.
When run, the menu item has no content in the Icon space, and Visual Studio's output contains an error that doesn't seem like it ought to be happening.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ContextMenu', AncestorLevel='1''. BindingExpression:Path=PlacementTarget.RandomBrush; DataItem=null; target element is 'Rectangle' (Name=''); target property is 'Fill' (type 'Brush')
Here's the XAML for the control:
<UserControl x:Class="ContextMenuItemIconTest.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Do something">
<MenuItem.Icon>
<Rectangle Width="16" Height="16" Fill="{Binding PlacementTarget.RandomBrush, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
</Grid>
And the code behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ContextMenuItemIconTest
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
ContextMenuOpening += UserControl1_ContextMenuOpening;
}
void UserControl1_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Random r = new Random();
RandomBrush = new SolidColorBrush(Color.FromRgb((byte)r.Next(256), (byte)r.Next(256), (byte)r.Next(256)));
}
#region RandomBrush (Dependency Property)
public Brush RandomBrush
{
get { return (Brush)GetValue(RandomBrushProperty); }
set { SetValue(RandomBrushProperty, value); }
}
public static readonly DependencyProperty RandomBrushProperty =
DependencyProperty.Register(
"RandomBrush",
typeof(Brush),
typeof(UserControl1),
new PropertyMetadata(new SolidColorBrush(Colors.Blue)));
#endregion
}
}
Not sure if there is a better solution but I think the scenario here is very tricky. The Icon content seems to be detached completely from the visual tree. So you cannot use Binding with RelativeSource or ElementName and strangely even setting the Source to some {x:Reference} causes some cyclic reference error.
I just could think of this work-around, a little hacky but it's acceptable. There is an interesting knowledge about the Freezable object. Binding inside it (set for some property) can use RelativeSource as well as ElementName even when it's just declared as a resource (added to Resources). So in this case we can try using some object deriving from Freezable to act as the proxy. Because this proxy is declared as a resource, we can set the Binding inside Icon with Source being set to some StaticResource referencing the proxy. Then it would work. There are many objects deriving from Freezable for your choice, you can even create your own class. But I would like to use something existing here. The DiscreteObjectKeyFrame is the most suitable object to use. Technically its Value property can hold any kind of object. Now's the working code:
<ContextMenu>
<ContextMenu.Resources>
<!-- the proxy here -->
<DiscreteObjectKeyFrame x:Key="o" KeyTime="0"
Value="{Binding PlacementTarget.RandomBrush,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu.Resources>
<MenuItem Header="Do something">
<MenuItem.Icon>
<Rectangle Width="16" Height="16"
Fill="{Binding Value, Source={StaticResource o}}" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>

Creating a WPF Window that allows zooming and panning

I want to create a Window that will hold several controls. However, I would like the user to be able to pan around and zoom in and out to see larger versions of those controls.
I don't even know where to begin looking.
I was going to start at ScaleTransform that responds to the use of the scroll button on the mouse but I am not sure if that is the best idea.
Just need a push in the right direction.
thanks!
This might be a good candidate for a Viewbox.
See here: http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox(v=vs.110).aspx
Basically, you can Wrap the entire contents of the Window into a Viewbox like so:
<Window>
<Viewbox>
<!-- content here -->
</Viewbox>
</Window>
and then bind to the Viewbox control's width and height to simulate the zooming. For a quick test, you could just listen to scroll wheel events via code-behind, name the Viewbox control, and access the Viewbox directly at change the values there.
Edit: here's a scenario I just found to get you started. They are using an image, but it's the exact same concept that I described above.
http://www.c-sharpcorner.com/uploadfile/yougerthen/working-with-wpf-viewbox-control/
Edit2: Quick working example using mouse-scroll
Xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
MouseWheel="MainWindow_OnMouseWheel">
<Grid>
<Viewbox x:Name="ZoomViewbox" Stretch="Fill">
<StackPanel>
<Label Content="Label" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" />
</StackPanel>
</Viewbox>
</Grid>
</Window>
C#:
using System.Windows;
using System.Windows.Input;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ZoomViewbox.Width = 100;
ZoomViewbox.Height = 100;
}
private void MainWindow_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
UpdateViewBox((e.Delta > 0) ? 5 : -5);
}
private void UpdateViewBox(int newValue)
{
if ((ZoomViewbox.Width >= 0) && ZoomViewbox.Height >= 0)
{
ZoomViewbox.Width += newValue;
ZoomViewbox.Height += newValue;
}
}
}
}
You can get functionality out of a ScrollViewer and a ScaleTransform. Here's an example:
<Window x:Class="CSharpWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- This ScrollViewer enables the panning -->
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<!-- This StackPanel is the container for the zoomable/pannable content. -->
<!-- Any container control (StackPanel, DockPanel, Grid, etc) may be used here. -->
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
<!-- This ScaleTransform implements the zooming and is bound the Value of the ZoomSlider -->
<StackPanel.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=ZoomSlider, Path=Value}" ScaleY="{Binding ElementName=ZoomSlider, Path=Value}" />
</StackPanel.LayoutTransform>
<!-- Here is your custom content -->
<Button>Foo</Button>
<Button>Bar</Button>
</StackPanel>
</ScrollViewer>
<!-- This Slider controls the zoom level -->
<Slider x:Name="ZoomSlider" Orientation="Horizontal" Grid.Row="1" Minimum="0.0" Maximum="8.0" LargeChange="0.25" SmallChange="0.01" Value="1.0" />
</Grid>
</Window>
Simple solution :
private void Window_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (Keyboard.Modifiers != ModifierKeys.Control)
return;
if (e.Delta < 0 && _scale > 0.7)
{
_scale -= 0.1;
MainGrid.LayoutTransform = new ScaleTransform(_scale, _scale);
}
else if (e.Delta > 0 && _scale < 1.5)
{
_scale += 0.1;
MainGrid.LayoutTransform = new ScaleTransform(_scale, _scale);
}
}

Child elements of scrollviewer preventing scrolling with mouse wheel?

I'm having a problem getting mouse wheel scrolling to work in the following XAML, which I have simplified for clarity:
<ScrollViewer
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible"
CanContentScroll="False"
>
<Grid
MouseDown="Editor_MouseDown"
MouseUp="Editor_MouseUp"
MouseMove="Editor_MouseMove"
Focusable="False"
>
<Grid.Resources>
<DataTemplate
DataType="{x:Type local:DataFieldModel}"
>
<Grid
Margin="0,2,2,2"
>
<TextBox
Cursor="IBeam"
MouseDown="TextBox_MouseDown"
MouseUp="TextBox_MouseUp"
MouseMove="TextBox_MouseMove"
/>
</Grid>
</DataTemplate>
</Grid.Resources>
<ListBox
x:Name="DataFieldListBox"
ItemsSource="{Binding GetDataFields}"
SelectionMode="Extended"
Background="Transparent"
Focusable="False"
>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem"
>
<Setter
Property="Canvas.Left"
Value="{Binding dfX}"
/>
<Setter
Property="Canvas.Top"
Value="{Binding dfY}"
/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</ScrollViewer>
Visually, the result is an area of some known size where DataFields read from a collection can be represented with TextBoxes which have arbitrary position, size, et cetera. In cases where the ListBox's styled "area" is too large to display all at once, horizontal and vertical scrolling is possible, but only with the scroll bars.
For better ergonomics and sanity, mouse wheel scrolling should be possible, and normally ScrollViewer would handle it automatically, but the ListBox appears to be handing those events such that the parent ScrollViewer never sees them. So far I have only been able to get wheel scrolling working be setting IsHitTestVisible=False for either the ListBox or the parent Grid, but of course none of the child element's mouse events work after that.
What can I do to ensure the ScrollViewer sees mouse wheel events while preserving others for child elements?
Edit: I just learned that ListBox has a built-in ScrollViewer which is probably stealing wheel events from the parent ScrollViewer and that specifying a control template can disable it. I'll update this question if that resolves the problem.
You can also create a behavior and attach it to the parent control (in which the scroll events should bubble through).
// Used on sub-controls of an expander to bubble the mouse wheel scroll event up
public sealed class BubbleScrollEvent : Behavior<UIElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;
base.OnDetaching();
}
void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
e2.RoutedEvent = UIElement.MouseWheelEvent;
AssociatedObject.RaiseEvent(e2);
}
}
<SomePanel>
<i:Interaction.Behaviors>
<viewsCommon:BubbleScrollEvent />
</i:Interaction.Behaviors>
</SomePanel>
Specifying a ControlTemplate for the Listbox which doesn't include a ScrollViewer solves the problem. See this answer and these two MSDN pages for more information:
ControlTemplate
ListBox Styles and Templates
Another way of implementing this, is by creating you own ScrollViewer like this:
public class MyScrollViewer : ScrollViewer
{
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
var parentElement = Parent as UIElement;
if (parentElement != null)
{
if ((e.Delta > 0 && VerticalOffset == 0) ||
(e.Delta < 0 && VerticalOffset == ScrollableHeight))
{
e.Handled = true;
var routedArgs = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);
routedArgs.RoutedEvent = UIElement.MouseWheelEvent;
parentElement.RaiseEvent(routedArgs);
}
}
base.OnMouseWheel(e);
}
}
I know it's a little late but I have another solution that worked for me. I switched out my stackpanel/listbox for an itemscontrol/grid. Not sure why the scroll events work properly but they do in my case.
<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<StackPanel Orientation="Vertical">
<ListBox ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
became
<ScrollViewer VerticalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding DrillingConfigs}" Margin="0,5,0,0" Grid.Row="0">
<ItemsControl.ItemTemplate>
<DataTemplate>
isHitTestVisible=False in the child works great for me
Edit This isnt a good way to do it

WPF unwanted grid splitter behaviour

I have a simple grid with 3 columns (one of which contains a grid splitter). When resizing the grid and the left column reaches its minimum width, instead of doing nothing it increases the width of the right column. Could anyone help me stop this?
I can't set the max width of the right column, because the grid itself also resizes.
Here's some sample code that shows the problem. While resizing, move the mouse over the red area:
XAML:
<Grid DockPanel.Dock="Top" Height="200">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="200" Width="*" />
<ColumnDefinition Width="3" />
<ColumnDefinition MinWidth="120" Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" />
<DockPanel LastChildFill="True" Grid.Row="0" Grid.Column="2" >
<Rectangle DockPanel.Dock="Right" Width="20" Fill="Blue" />
<Rectangle Fill="Green" />
</DockPanel>
<GridSplitter Background="LightGray" Grid.Row="0" Grid.Column="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
I know this is a bit late, but I just run into this problem, and here is my solution. Unfortunately it is not general enough, it only works for a grid with two columns, but it can probably be adapted farther. However, it solves the described problem and my own, so here goes:
The solution consists in a hack or workaround, however you want to call it. Instead of declaring MinWidth for both the left and right column, you declare a MinWidth and a MaxWidth for the first column. This means that the GridSplitter won't move right of a defined location. So far, so good.
The next problem is that if we have a resizable container (the window in my case), this is not enough. It means that we cannot enlarge the left column as much as we want, even though there might be plenty of space for the second one. Fortunately, there is a solution: binding on the Grid ActualWidth and using an addition converter. The converter parameter will actually be the desired MinWidth for the right column, obviously the negative value, since we need to subtract it from the Grid Width. You could also use a SubtractConvertor, but that is up to you.
Here goes the xaml and code:
<Grid Background="{DynamicResource MainBackground}" x:Name="MainGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="100" MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType=Grid}, Converter={Converters:AdditionConverter}, ConverterParameter=-250}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<GridSplitter Width="3" VerticalAlignment="Stretch" Grid.Column="0"/>
<!-- your content goes here -->
</Grid>
and the converter:
[ValueConversion(typeof(double), typeof(double))]
public class AdditionConverter : MarkupExtension, IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double dParameter;
if (targetType != typeof(double) ||
!double.TryParse((string)parameter, NumberStyles.Any, CultureInfo.InvariantCulture, out dParameter))
{
throw new InvalidOperationException("Value and parameter passed must be of type double");
}
var dValue = (double)value;
return dValue + dParameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
#region Overrides of MarkupExtension
/// <summary>
/// When implemented in a derived class, returns an object that is set as the value of the target property for this markup extension.
/// </summary>
/// <returns>
/// The object value to set on the property where the extension is applied.
/// </returns>
/// <param name="serviceProvider">Object that can provide services for the markup extension.
/// </param>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#endregion
}
I hope this helps,
Mihai Drebot
This approach is a hack but can be used for the desired effect. Try putting an event handler on the Window.SizeChanged event to set the maxWidth of the first columndef. For example:
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
int borderBuffer = 9;
// AppWindow Size Splitter Width Far Column Min FudgeFactor
Col0.MaxWidth = e.NewSize.Width - Col1.Width.Value - Col2.MinWidth - borderBuffer;
}
I needed to use this method to prevent a third party control in the top row of the grid from having some undesirable wrapping resulting from when the gridSplitter disregards the min/max width of the column with width set to "Auto". The borderBuffer may need to be adjusted for your case. The borderBuffer in my case didn't make perfect sense based on the geometry/column/border widths - it was just the magic number that worked for my layout.
If someone can come up with a cleaner solution, I'd love to use it instead. This solution reminds me of painfully trying to force VB6 to resize controls - yuck. But for now, it beats having items on the top row of my grid from getting hidden due to unexpected wrapping.
I got this undesirable behaviour to stop by changing the * in the columndefinition to Auto and the 240 to *.

Resources