Path drawing and data binding - wpf

I am looking for a way to be able to use the wpf Path element to draw a path that will represent a route on the map. I have the Route class that contains a collection of vertices and would like to use it for binding. I don't really know how to even start..
Any hints?

The main thing you'll need for the binding is a converter that turns your points into Geometry which the path will need as Data, here is what my one-way converter from a System.Windows.Point-array to Geometry looks like:
[ValueConversion(typeof(Point[]), typeof(Geometry))]
public class PointsToPathConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Point[] points = (Point[])value;
if (points.Length > 0)
{
Point start = points[0];
List<LineSegment> segments = new List<LineSegment>();
for (int i = 1; i < points.Length; i++)
{
segments.Add(new LineSegment(points[i], true));
}
PathFigure figure = new PathFigure(start, segments, false); //true if closed
PathGeometry geometry = new PathGeometry();
geometry.Figures.Add(figure);
return geometry;
}
else
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Now all that is really left is to create an instance of it and use it as the converter for the binding. What it might look like in XAML:
<Grid>
<Grid.Resources>
<local:PointsToPathConverter x:Key="PointsToPathConverter"/>
</Grid.Resources>
<Path Data="{Binding ElementName=Window, Path=Points, Converter={StaticResource ResourceKey=PointsToPathConverter}}"
Stroke="Black"/>
</Grid>
If you need the binding to update automatically you should work with dependency properties or interfaces like INotifyPropertyChanged/INotifyCollectionChanged
Hope that helps :D

Also you can try it this way:
public static class PathStrings
{
public const string Add = "F1 M 22,12L 26,12L 26,22L 36,22L 36,26L 26,26L 26,36L 22,36L 22,26L 12,26L 12,22L 22,22L 22,12 Z";
}
Then in the resource create a PathString
<Window.Resources>
<yourNamespace:PathStrings x:Key="pathStrings"/>
</Window.Resources>
then bind it this way:
<Path Stroke="Black" Fill="Black"
Data="{Binding Source={StaticResource pathStrings}, Path=Add}"></Path>

Related

How to draw a collection of points as separate circles?

My view model has a PointCollection property like this:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private PointCollection points;
public PointCollection Points
{
get { return points; }
set
{
points = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Points)));
}
}
}
This is usually shown as a polyline:
<Polyline Points="{Binding Points}" Stroke="Black" StrokeThickness="2"/>
How would I efficiently show it as a collection of separate circles?
It may well be done by an ItemsControl with e.g. a Path element with an EllipseGeometry in its ItemTemplate, however that would involve a large number of UI elements, which may not perform well for a large number of Points in the PointsCollection.
A Binding Converter like shown below could convert an IEnumerable<Point> into a StreamGeometry that consists of a set of zero-length lines.
These could be drawn as circles by a Path with StrokeStartLineCap and StrokeEndLineCap set to Round.
public class LinePointsConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var geometry = new StreamGeometry();
var points = value as IEnumerable<Point>;
if (points != null && points.Any())
{
using (var sgc = geometry.Open())
{
foreach (var point in points)
{
sgc.BeginFigure(point, false, false);
sgc.LineTo(point, true, false);
}
}
}
return geometry;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The Path would look like this:
<Path Data="{Binding Points, Converter={StaticResource LinePointsConverter}}"
Stroke="Black" StrokeThickness="5"
StrokeStartLineCap="Round" StrokeEndLineCap="Round"/>

Dynamically switching between canvases wpf

I have multiple canvas images of different types (image source, geometry, path) and wish to only show 1 depending on a string binding.
whats the best way to do this?
i'd like it to be reusable so i can place this code inside a user control and then have many of these images around the app and i select which 1 is shown.
Like so:
<CanvasImage Image="Pie"/>
<CanvasImage Image="Dog"/>
Would it be too computationally expensive to have them all declared in the user control view and use visibility bindings
Pie canvas example:
<canvas>
<Data ="m24,98,07">
</canvas>
Dog canvas example:
<canvas>
<image source="">
<canvas>
This converter return an image source directly, depending on the value it receives.
namespace TestTreeView.Views
{
public class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string file = "";
string v = value as string;
switch (v)
{
case "Pie":
file = #".\path\to\your\pie.jpg";
break;
case "Dog":
file = #".\path\to\your\dog.jpg";
break;
default:
return null;
}
return new BitmapImage(new Uri(file, UriKind.RelativeOrAbsolute));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Usage in XAML:
<Window xmlns:local="clr-namespace:YourNamespace.Views" ...>
<Window.Resources>
<local:StringToImageConverter x:Key="stringToImageConverter"/>
</Window.Resources>
<Grid>
<Canvas>
<Image Source="{Binding YourString, Converter={StaticResource stringToImageConverter}}"/>
</Canvas>
</Grid>
</Window>
Original answer
I think you need to use a Converter.
It will take a ConverterParameter, a String, that will tell what the binded value is expected to be, and return a Visiblity to indicate if the canvas should be visible or not.
namespace YourNamespace.Views
{
public class StringToCanvasVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string v = value as string;
string p = parameter as string;
return (v != null && p != null && v == p) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Usage in XAML:
<Window xmlns:local="clr-namespace:YourNamespace.Views" ...>
<Window.Resources>
<local:StringToVisibilityConverter x:Key="stringToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Canvas Visibility="{Binding YourString, Converter={StaticResource stringToVisibilityConverter}, ConverterParameter=Pie}"/>
<Canvas Visibility="{Binding YourString, Converter={StaticResource stringToVisibilityConverter}, ConverterParameter=Dog}"/>
</Grid>
</Window>

Automatically capitalize first letter in TextBoxes

I am creating a wpf application. I have to make all textbox first letter to capital, if a user entered in small then it should be formatted in capital on mouse out.I need the best way to do it, please someone help me.
The best way of doing it greatly depends on how you are doing your app, but #H.B.'s answer is probably the way to go.
For the sake of completeness, another way if doing it would be to use a converter like so:
<!-- Your_Window.xaml -->
<Window x:Class="..."
...
xmlns:cnv="clr-namespace:YourApp.Converters">
<Window.Resources>
<cnv.CapitalizeFirstLetterConverter x:Key="capFirst" />
</Window.Resources>
...
<TextBox Text="{Binding Path=SomeProperty, Converter={StaticResource capFirst}}" />
This assumes that your window's data context is set to an instance of a class that has a read/write property named SomeProperty of type string.
The converter itself would be something like this:
// CapitalizeFirstLetterConverter.cs
using System;
using System.Data;
using System.Globalization;
namespace YourApp.Converters {
[ValueConversion(typeof(string), typeof(string))]
public class CapitalizeFirstLetterConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
// this will be called after getting the value from your backing property
// and before displaying it in the textbox, so we just pass it as-is
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
// this will be called after the textbox loses focus (in this case) and
// before its value is passed to the property setter, so we make our
// change here
if (value is string) {
var castValue = (string)value;
return char.ToUpper(castValue[0]) + castValue.Substring(1);
}
else {
return value;
}
}
}
}
You can learn more about converters here.
You could put a style into the Application.Resources to handle LostFocus on all TextBoxes, then you just need to change the Text property accordingly.
<!-- App.xaml - Application.Resources -->
<Style TargetType="{x:Type TextBox}">
<EventSetter Event="LostFocus" Handler="TextBox_LostFocus" />
</Style>
// App.xaml.cs - App
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var tb = (TextBox)sender;
if (tb.Text.Length > 0)
{
tb.Text = Char.ToUpper(tb.Text[0]) + tb.Text.Substring(1);
}
}
I'm a bit late to the game, but if anybody else needs it this dll capitalizes the first letter in realtime. For example, you don't need to mouse out.
http://www.mardymonkey.co.uk/blog/auto-capitalise-a-text-control-in-wpf/
Perhaps you can use a converter but not a converter like #ssarabando, because it is bugged.
Here's the code of the converter:
using System;
using System.Globalization;
using System.Windows.Data;
namespace SistemaContable.GUI.WPF.Converters
{
public class CapitalizeFirstLetter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
string stringToTitleCase = culture.TextInfo.ToTitleCase(value.ToString());
return stringToTitleCase;
}
else
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
}
}
You need to reference it in a "ResourceDictionary" or in your "App.xaml":
<ResourceDictionary xmlns:converters="clr-namespace:SistemaContable.GUI.WPF.Converters">
<converters:CapitalizeFirstLetter x:Key="CapitalizeFirstLetter"/>
</ResourceDictionary>
And you can use it like this:
<TextBox x:Name="txtNombre" Text="{Binding Usuario.Nombre, Converter={StaticResource CapitalizeFirstLetter}, UpdateSourceTrigger=PropertyChanged}"/>

Binding in WPF to element of array specified by property

Say I've got some TextBlocks on my UI, something like so:
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding DessertIndex}" />
<TextBlock Text="{Binding Food[2]}" />
<TextBlock Text="{Binding Food[{Binding DessertIndex}]}" />
</StackPanel>
and in my code behind I've got something like this:
public partial class MainWindow : Window
{
public int DessertIndex
{
get { return 2; }
}
public object[] Food
{
get
{
return new object[]{"liver", "spam", "cake", "garlic" };
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
The first two TextBlocks display fine for me, displaying 2 and 'cake' respectively. The third one doesn't accomplish what I'd like, namely use the DessertIndex property to index into that array and also display 'cake'. I did a little searching here on SO for a similar question but didn't find one. Ultimately, I don't want to specify values like 2 in my .xaml file and would like to rely upon a property instead for indexing into that array. Is this possible? If so, what am I doing wrong here?
EDIT:
So what I more closely have is a situation where the data is a List of these object[] and I'm using the above StackPanel as part of a DataTemplate for a ListBox. So the idea, as Mark Heath suggests below, of using a property that dereferences the array doesn't seem to work as I'd want. Ideas?
Another alternative is to use MultiBinding with a converter:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<local:FoodIndexConverter x:Key="foodIndexConverter" />
</StackPanel.Resources>
<TextBlock Text="{Binding DessertIndex}" />
<TextBlock Text="{Binding Food[2]}" />
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource foodIndexConverter}">
<Binding Path="DessertIndex" />
<Binding Path="Food"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Then in the code-behind, the converter is defined something like this:
namespace WpfApplication1
{
public class FoodIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values == null || values.Length != 2)
return null;
int? idx = values[0] as int?;
object[] food = values[1] as object[];
if (!idx.HasValue || food == null)
return null;
return food[idx.Value];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
if you are going to the trouble of having a DesertIndex property on your DataContext, why not a property that dereferences the Food array with DesertIndex:
public object SelectedFood
{
get { return Food[DessertIndex]; }
}
public int DessertIndex
{
get { return 2; }
}
public object[] Food
{
get
{
return new object[]{"liver", "spam", "cake", "garlic" };
}
}
then you can bind directly to that:
<TextBlock Text="{Binding SelectedFood}" />
This is essentially the "MVVM" approach: make the datacontext object have properties that are just right for binding to.
Just To add on the great answer by Colin Thomsen.
You could also use C# dynamic keyword to make this solution work with pretty much every container type. Or even bind to multidimensional containers "{Binding Food[{Binding DessertIndex1}][{Binding DessertIndex2}]}"
public class ContainerDoubleAccessConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
dynamic idx1 = values[0];
dynamic idx2 = values[1];
dynamic container = values[2];
return container[idx1][idx2];
}
catch (System.Exception err)
{
DebugTrace.Trace("bad conversion " + err.Message);
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}

How to get the height of the title bar of the main application windows?

I'm using prism to load views to region. The problem is the loaded view overlapped the title bar of the main windows - the bar contains caption, close/minimize/maximize buttons. How can I get the title bar's height? Prefer to get it right in the xaml codes.
After a while, I figure it out:
<Window xmlns:local="clr-namespace:System.Windows;assembly=PresentationFramework">
<YourView Height="{x:Static local:SystemParameters.WindowCaptionHeight}" />
</Window>
Hope that helps!
SystemParameters.WindowCaptionHeight is in pixels whereas WPF needs screen cordinates. You have to convert it!
<Grid>
<Grid.Resources>
<wpfApp1:Pixel2ScreenConverter x:Key="Pixel2ScreenConverter" />
</Grid.Resources>
<YourView Height="{Binding Source={x:Static SystemParameters.WindowCaptionHeight},Converter={StaticResource Pixel2ScreenConverter}}" />
</Grid>
aa
public class Pixel2ScreenConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double pixels = (double) value;
bool horizontal = Equals(parameter, true);
double points = 0d;
// NOTE: Ideally, we would get the source from a visual:
// source = PresentationSource.FromVisual(visual);
//
using (var source = new HwndSource(new HwndSourceParameters()))
{
var matrix = source.CompositionTarget?.TransformToDevice;
if (matrix.HasValue)
{
points = pixels * (horizontal ? matrix.Value.M11 : matrix.Value.M22);
}
}
return points;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I think you can do this since .NET Framework 4.5.
Reference: https://learn.microsoft.com/en-us/dotnet/api/system.windows.shell.windowchrome.captionheight?view=windowsdesktop-7.0#system-windows-shell-windowchrome-captionheight
Here is what you can do:
double title_height = (new WindowChrome()).CaptionHeight;

Resources