Is there a way to bind a value to the absolute position of a control using XAML?
I have a Line element that I would like to be drawn between two Buttons in my application. I was thinking that binding the starting point of the Line to the position of the Button would be the easiest way to make this happen, using RelativeSource somehow.
It seems you want something like this:
<UserControl x:Class="PracticeSample.MyButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button x:Name="button" Content="Add" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<Line Stroke="Black" X1="0" Y1="0" HorizontalAlignment="Center" X2="{Binding ElementName=button, Path=ActualWidth}" Y2="{Binding ElementName=button, Path=ActualHeight}"/>
</Grid>
use this MyButton in your pages in place of Button,
Edit:
if you want to draw line between two controls
don't use above code sample but try this directly in your page:
<Canvas HorizontalAlignment="Left" Margin="10">
<Button x:Name="button2" Content="Add" Canvas.Left="10" Canvas.Top="5"/>
<Button Name="button" Content="Refresh Control" Canvas.Left="100" Canvas.Top="50"/>
<Line Stroke="Black" X1="{Binding Path=(Canvas.Left),ElementName=button2}" Y1="{Binding Path=(Canvas.Top), ElementName=button2}" X2="{Binding (Canvas.Left), ElementName=button}" Y2="{Binding (Canvas.Top), ElementName=button}"/>
</Canvas>
Hope this helps!
Define a Template with Button and Line positioned at right places wherever you like and then use this Template at the place of Button.
Related
I have a simple window with an image and a button. When the user resizes the window, I would like the button to be resized with the image in the exact same proportions. Below is an example of the desired behaviour I would like to have. The button around the head of the dog just extends and translates with the image.
I managed to have the above behaviour with a "Path" (above in blue), but I didn't manage to get the same behaviour with a button. I would like to perform some actions when the user clicks on the button.
Here is the xaml code I used for the working "Path" senario:
<Grid>
<Canvas x:Name="polylineCanvas" Grid.Row="1">
<Image x:Name="imgTraining"
Width="{Binding ActualWidth, ElementName=polylineCanvas, Mode=OneWay}"
Height="{Binding ActualHeight, ElementName=polylineCanvas, Mode=OneWay}"
Stretch="Uniform"
HorizontalAlignment="Left"
VerticalAlignment="Top">
</Image>
<Path Stroke="Blue" StrokeThickness="3">
<Path.Data>
<PathGeometry x:Name="polyline">
<PathGeometry.Transform>
<ScaleTransform
ScaleX="{Binding ActualWidth, ElementName=imgTraining}"
ScaleY="{Binding ActualHeight, ElementName=imgTraining}"/>
</PathGeometry.Transform>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
</Grid>
I tried to apply the same logic by adding a button instead of a Path. In this case, the button streches to the size of the window (but not to the size of the image):
Here is the xaml code I added for the button:
<Button x:Name="buttSelection" Opacity="0.5" Background="#FF000CFF">
<Button.LayoutTransform>
<ScaleTransform
ScaleX="{Binding ActualWidth, ElementName=imgTraining}"
ScaleY="{Binding ActualHeight, ElementName=imgTraining}"/>
</Button.LayoutTransform>
</Button>
Questions:
How could I scale my button in the same proportion as the image?
How could I set the size and position of this button to only be a part of the image (and not to be as big as the image itself)?
Thanks a lot for the support!
PS: I am very new to wpf/xaml, so there is a big chance that I am not doing things in the "intended way" or that there is a completely different solution.
You should put both the Image and the Button in a common Viewbox, which would automatically scale them together. There is usually no need to bind the size of an element to the actual size of another. This is done by using appropriate layout panels.
Inside the Viewbox, there would be a Grid as common parent, and a Canvas to position the Button. The Button Template would contain a Border around a ContentPresenter that could optionally show the Button's Content.
<Grid>
<Viewbox>
<Grid>
<Image x:Name="imageTraining"/>
<Canvas>
<Button x:Name="buttonSelection" Width="200" Height="200"
Canvas.Left="200" Canvas.Top="200">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="10"
BorderBrush="Blue"
Background="Transparent">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Canvas>
</Grid>
</Viewbox>
</Grid>
I am trying to draw a line under the button, well in fact, last element within the button. The button has three element inside in vertical: image, label, and finally the line I am trying to put. Line must be the same widht than button. Below the code that is not working (line does not appear):
<Button Name="btnDelete" HorizontalAlignment="Center" Margin="30,10" Command="{Binding DeleteCommand}">
<Button.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<Image Height="36" Width="36" Stretch="UniformToFill" Source="/MyResources;component/PNG/Delete.png"/>
<Label HorizontalAlignment="Center">Delete</Label>
<Line Stroke="Orange" X1="0" Y1="25" X2="{Binding ElementName=btnDelete, Path=Width}" Y2="25" />
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
If you just want a straight Line then use a Border. If you just need it horizontal then use it like :
<Button Name="btnDelete" HorizontalAlignment="Center" Margin="30,10" Command="{Binding DeleteCommand}">
<Button.Template>
<ControlTemplate>
<Border BorderBrush="Orange" BorderThickness="0 0 0 3">
<StackPanel>
<Image Height="36" Width="36" Stretch="UniformToFill" Source="/MyResources;component/PNG/Delete.png"/>
<Label HorizontalAlignment="Center">Delete</Label>
</StackPanel>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Use Margin and Padding to adjust it - when needed.
Also u should use Paths instead of Image. You can convert them directly in XAML-Paths with Incscape.
Additional Information Path
A Path is a good way to show Icons in XAML. They can look like:
<Path Stroke="Black" Fill="Gray"
Data="M 10,100 C 10,300 300,-200 300,100" />
You can give them different Brushes and they scale very well in the most scenarious. Inkscape has a good feature - to create the Path out of your Image (svg / png / jpg etc).
There are also Icon-Packs that allready have them like font awesome.
I try to get into creation of custom controls with for WPF. I found many good
tutorials and advises on the web so I started width a really simple example to get
my hands dirty and get some practice. I figured out that the issue stumbled across
is not really related to the subject of custom controls. So I extracted the xaml code to a simple wpf form.
<Window x:Class="WpfVerticalAigmentTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="200">
<Grid>
<Grid Height="40" Background="LightCyan" VerticalAlignment="Center">
<Path Stroke="Red"
StrokeThickness="20" VerticalAlignment="Center" >
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="100,0"></LineGeometry>
</Path.Data>
</Path>
</Grid>
</Grid>
My expectation was to get a line centered in the grid and claiming the half of the stroke thickness on each side from the center. But as the linked image shows differs from my expectation.
"Resulting visualization"
So it look like I missed a detail about the line shape or linegeomtry. How do I get the the line displayed as shown in the following image?
"Expected result"
You need to match the Width and Height of the LineGeometry to the Width and Height of the Path and set the VerticalAlignment property to Bottom:
<Grid Height="20" Width="200" Background="LightCyan" VerticalAlignment="Center">
<Path Stroke="Red" StrokeThickness="20" VerticalAlignment="Bottom">
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="200,0"></LineGeometry>
</Path.Data>
</Path>
</Grid>
If your goal is your the expectaions, and not the way how u have reached this, I could prefer to you this:
<Grid>
<Grid Height="40" Background="LightCyan" VerticalAlignment="Center">
<Border BorderThickness="10" VerticalAlignment="Center" BorderBrush="Red" />
</Grid>
</Grid>
The problem here is that the starting point of the XY Coordinates of the Path starts on the top left, and the stroke expands in both directions but thereby only makes the Path bigger to the bottom (I can't really tell you why, but that's just what seems to happen).
You can see this pretty good in the Design View:
To work around this simply move your Y Coordinates down half of the stroke size.
<Grid Height="40"
VerticalAlignment="Center"
Background="LightCyan">
<Path VerticalAlignment="Center"
Stroke="Red"
StrokeThickness="20">
<Path.Data>
<LineGeometry StartPoint="0,10" EndPoint="100,10" />
</Path.Data>
</Path>
</Grid>
Or wrap it in another control (Canvas is the commonly used controls for Paths) with the desired height:
<Grid Height="40"
VerticalAlignment="Center"
Background="LightCyan">
<Canvas Height="20" VerticalAlignment="Center">
<Path Stroke="Red"
StrokeThickness="20">
<Path.Data>
<LineGeometry StartPoint="0,10" EndPoint="100,10" />
</Path.Data>
</Path>
</Canvas>
</Grid>
And you are good to go:
I have a layout contained within a ScrollViewer in which I need to draw a horizontal dashed line that stretches to the full width of the container. The closest I've managed is the following
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<Line HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Stroke="Black"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeDashArray="2 2" StrokeThickness="1" />
</StackPanel>
</ScrollViewer>
This nearly works, however once the container (in my case a window) has been enlarged, the line doesn't shrink back down to the appropriate size when the container is sized back down. The below is the screenshot of the same window after I have horizontally sized the window up and down.
Note that the fact that the line is dashed is important as it means that solutions that involve stretching the line don't work (the dashes appear stretched).
I know that this is because of the X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" binding (by design the line is always the widest thing in the scrollable region, so when I size the window down the scrollable region the line defines the width of the scrollable region), however I can't think of a solution.
How can I fix this problem?
Screenshot of why using ViewportWidth doesn't work
I realised that what I needed was for the Line to ask for zero space during the measure step of layout, but then use up all the available space during the arrange step. I happened to stumble across the question Make WPF/SL grid ignore a child element when determining size which introduced the approach of using a custom decorator which included this logic.
public class NoSizeDecorator : Decorator
{
protected override Size MeasureOverride(Size constraint) {
// Ask for no space
Child.Measure(new Size(0,0));
return new Size(0, 0);
}
}
(I was hoping that some existing layout control incorporated this logic to avoid having to write my own layout logic, however the logic here is so simple that I'm not really that fussed). The modified XAML then becomes
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<local:NoSizeDecorator Height="1">
<Line Stroke="Black" HorizontalAlignment="Stretch"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeDashArray="2 2" StrokeThickness="1" />
</local:NoSizeDecorator>
</StackPanel>
</ScrollViewer>
This works perfectly
You may put a very long Line in a left-aligned Canvas with zero Width and ClipToBounds set to false.
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<Canvas HorizontalAlignment="Left" Width="0" ClipToBounds="False">
<Line Stroke="Black" StrokeDashArray="2 2" X2="10000"/>
</Canvas>
</StackPanel>
</ScrollViewer>
In my project I want to display a small logo on the side of a custom control. Since I have no canvas I thought maybe a Visual Brush would be a good Idea to place the logo in the background.
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Width="200" Height="200" Fill="Red" />
</VisualBrush.Visual>
</VisualBrush>
But the Rectangle I am using right now is not 200x200. It takes the complete available space. Thats not what I want. I also tried a Viewbox and set the stretch property but the result is the same because in the end I don't need a simple Rectangle but a canvas with many path objects as children. A Viewbox supports only one child.
This there any way to get around this problem?
You need to set TileMode, Stretch, AlignmentX and AlignmentY properties on your VisualBrush:
<VisualBrush TileMode="None" Stretch="None" AlignmentX="Left" AlignmentY="Top">
<VisualBrush.Visual>
<Rectangle Height="200" Width="200" Fill="Red"></Rectangle>
</VisualBrush.Visual>
</VisualBrush>
Add Grid and this Set Vertical alligment to Top and Horizontal alignment to Right
Sample code
<VisualBrush x:Key="myVisual">
<VisualBrush.Visual>
<Grid>
<Rectangle Height="200" Width="200" Fill="Red" HorizontalAlignment="Right" VerticalAlignment="Top" ></Rectangle>
</Grid>
</VisualBrush.Visual>
</VisualBrush>
For me, I set the following attribute on the VisualBrush, and the VisualBrush now looks exactly like a MediaElement:
Stretch="Uniform"