I want to extend a slider by a triangle pointing to a fixed position (the triangle will not be moved like the thumb).
The current code draws the triangle at position 0,0.
<Path Data="M 0 0 L 6 0 L 3 7 Z" Stroke="Black" Fill="Black"/>
The result looks like this:
In this example the slider represents a % value (the range is not necessarily 0 - 100). The triangle should be positioned at a fixed % value, which is provided via dependency property.
My question is: how do I relocate the triangle to point to that % value (not the thumbs value)?
With fixed ratio it would look like:
<Grid>
<Grid Margin="0,-10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*"/>
<ColumnDefinition Width="0"/>
<ColumnDefinition Width="75*"/>
</Grid.ColumnDefinitions>
<Canvas Grid.Column="1">
<Path Data="M -3 0 L 3 0 L 0 7 Z" Stroke="Black" Fill="Black"/>
</Canvas>
</Grid>
<Slider/>
</Grid>
Notice that I changed the path to center it on the canvas.
To make the ratio dynamic you need a converter to be able to bind it to a property (a double named Percent here)
<Window.Resources>
<local:GridLengthPercentConverter x:Key="GridLengthPercentConverter" />
</Window.Resources>
...
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding
Path=Percent,
Converter={StaticResource GridLengthPercentConverter},
ConverterParameter=first"/>
<ColumnDefinition Width="0"/>
<ColumnDefinition Width="{Binding
Path=Percent,
Converter={StaticResource GridLengthPercentConverter},
ConverterParameter=other"/>
</Grid.ColumnDefinitions>
<Canvas Grid.Column="1">
<Path Data="M -3 0 L 3 0 L 0 7 Z" Stroke="Black" Fill="Black"/>
</Canvas>
</Grid>
<Slider/>
</Grid>
Here's how the converter looks like:
public class GridLengthPercentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(parameter.ToString() == "first")
return new GridLength((double)value, GridUnitType.Star)
return new GridLength(100 - (double)value, GridUnitType.Star)
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Related
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 want something very simple in WPF, but I don't get it to work:
I have a grid with 2 columns: one * and one Auto. The second column contains a TextBlock. I need texttrimming to work with this TextBlock. This doesn't work currently, because the TextBlock goes outside the bounds of the grid.
Extra info:
The second column should be juste wide enough to contain the TextBlock. The first column should contain all remaining space. If the Grid isn't wide enough to contain the desired width of the TextBlock, the text should be trimmed.
The width of the Grid changes when resizing the window.
Nothing is static (not the text, no sizes), so hardcoded values can not be used.
ClipToBounds property doesn't fix this issue.
I can't bind MaxWidth of the TextBlock to the width of the column, otherwise the TextBlock will only getting smaller, but never bigger when resizing the window.
Code to reproduce the issue (for example in Kaxaml):
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" MaxWidth="200"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
Background="Red"
Text="Test tralalalalalalalalalala long string this should be trimmed!"
TextTrimming="CharacterEllipsis"/>
</Grid>
</DockPanel>
</Page>
Any suggestions?
Second solution:
Use a Converter like this:
namespace StackStuff{
class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is Double)
{
return (double)value - 200; // 200 = 100+100 form the grid margin
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
In the View, you will have:
xmlns:local="clr-namespace:StackStuff"
Then, you have to add the converter for it to be used:
<Window.Resources>
<local:WidthConverter x:Key="WidthConverter"/>
</Window.Resources>
And then you have to implement the converter:
<DockPanel Background="Green" x:Name="dock">
<Grid Height="20" Background="Blue" DockPanel.Dock="Top" Margin="100 0 100 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock MaxWidth="{Binding ActualWidth, Converter={StaticResource WidthConverter}, ElementName=dock}"
Grid.Column="1"
Hope this is what you wanted.
Working on wpf solution
i have created a Grid with different columns, a control is added in column 2.
Now i have a control outside grid and the Left margin should be same as the control inside the grid.
Can this be done?
We can of course take the very simple way of changing Margin using code.
For pure MVVM approach, you can do that using AttachedProperty.
Binding with a Convertor won't work here as Thickness type is not a DependencyObject.
If we simply bind the Margin of outer Button to inner Button, Entire Margin of outer Button will change, which we dont want. So, we need to preserve whole Margin except Left Margin. Left Margin can be changed using Binding. But how ? Our outer Button needs to have two Margin values, one original, and another coming from inner Button, so that original one can be changed. For this another margin, we can take help of Attached Property, as they allow us to extend a control.
AttachedProperty
public static BindingExpression GetLefMargin(DependencyObject obj)
{
return (BindingExpression)obj.GetValue(LefMarginProperty);
}
public static void SetLefMargin(DependencyObject obj, BindingExpression value)
{
obj.SetValue(LefMarginProperty, value);
}
// Using a DependencyProperty as the backing store for LefMargin. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LefMarginProperty =
DependencyProperty.RegisterAttached("LefMargin", typeof(BindingExpression), typeof(Window1), new PropertyMetadata(null, new PropertyChangedCallback(MarginCallback)));
private static void MarginCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement elem = d as FrameworkElement;
BindingExpression exp = e.NewValue as BindingExpression;
// Create a new Binding to set ConverterParameter //
Binding b = new Binding();
b.Converter = exp.ParentBinding.Converter;
b.ConverterParameter = elem.Margin;
b.Path = exp.ParentBinding.Path;
b.ElementName = exp.ParentBinding.ElementName;
b.Mode = exp.ParentBinding.Mode;
elem.SetBinding(FrameworkElement.MarginProperty, b);
}
Converter
public class LeftMarginCnv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double gridCtrlLeftMargin = ((Thickness)value).Left;
Thickness tgtCtrlMargin = (Thickness)parameter;
return new Thickness(gridCtrlLeftMargin, tgtCtrlMargin.Top, tgtCtrlMargin.Right, tgtCtrlMargin.Bottom);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage
<Grid>
<Grid Background="Red" Margin="29,55,52,125" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90*"/>
<ColumnDefinition Width="121*"/>
</Grid.ColumnDefinitions>
<Button x:Name="Btn" Content="Press" Margin="18,22,35,28" Grid.Column="1" Click="Btn_Click"/>
</Grid>
<Button local:Window1.LefMargin="{Binding Margin, ElementName=Btn, Converter={StaticResource LeftMarginCnvKey}}" Content="Button" HorizontalAlignment="Left" Margin="55,199,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
Outer Button will change its Left Margin if you change the Left margin of inner Button.
You can set the common style for both. Something like this.
<StackPanel>
<StackPanel.Resources>
<Style x:Key="commonstyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Margin" Value="10,0,0,0" />
</Style>
</StackPanel.Resources>
<TextBox x:Name="outside" Width="100" Height="70" Style="{StaticResource commonstyle}"/>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Width="100" Height="70" x:Name="inside" Grid.Column="2" HorizontalAlignment="Left" Style="{StaticResource commonstyle}"/>
</Grid>
</StackPanel>
(or)
Make it simple
<StackPanel>
<TextBox x:Name="outside" Width="100" Height="70" Margin="{Binding ElementName=inside, Path=Margin}"/>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Width="100" Height="70" x:Name="inside" Grid.Column="2" HorizontalAlignment="Left" Margin="20"/>
</Grid>
</StackPanel>
Hope that helps.
I have a grid with certain column settings:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*" MinWidth="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="100*" />
</Grid.RowDefinitions>
....Omitting content to keep this simple....
</grid>
When I resize the width of the containing control, the center column resizes as expected, to a point. Then it will start to clip the third column for no apparent reason (there's still room for the center column to shrink). How can I force WPF to only resize the center column, and only clip the third column if the center column's width is at 0?
EDIT:
Try using a MultiValueConverter to minimize the MaxWidth of the second column to the available space in the grid. The code below should do the trick:
Xaml:
<Grid.ColumnDefinitions>
<ColumnDefinition Name="col1" Width=".25*" MinWidth="200"/>
<ColumnDefinition Name="col2" Width="*">
<ColumnDefinition.MaxWidth>
<MultiBinding Converter="{StaticResource GridWidthConverter}">
<Binding ElementName="col1" Path="MinWidth"/>
<Binding ElementName="col3" Path="Width"/>
<Binding ElementName="Control" Path="ActualWidth"/>
</MultiBinding>
</ColumnDefinition.MaxWidth>
</ColumnDefinition>
<ColumnDefinition Name="col3" Width="240" />
</Grid.ColumnDefinitions>
Converter:
public class GridWidthConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col1 = System.Convert.ToDouble(values[0]);
var col2 = System.Convert.ToDouble(((GridLength)values[1]).Value);
var control = System.Convert.ToDouble(values[2]);
var maxWidth = control - (col1 + col2);
return maxWidth > 0 ? maxWidth : 0;
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
You may want to add some error checking to the converter but it should give you the idea.
I ended up figuring out the problem. Richard E led me in the right direction. Apparently the act of setting a * notation on a column and a minimum width caused the behavior I was experiencing. Specifically, when column 1 stopped shrinking due to hitting the minimum width, column 2 only continued to shrink at a rate that it would have if column 1 continued to shrink as well. This version of the xaml works correctly:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
I don't know if the behavior I encountered was intentional or not.
Thanks for the help!
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));
}