How to draw a collection of points as separate circles? - wpf

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"/>

Related

Binding to an element in the ObservableCollection

How do you make binding to a collection item in this case?
//Both collections have OnPropertyChanged("");
public ObservableCollection<Grid> ConnectorsGrids { get; set; }
public ObservableCollection<double> Connectors { get; set; }
//Coordinates collection
Connectors = new ObservableCollection<double>() { 10 , A - 20 };
ConnectorsGrids = new ObservableCollection<Grid>();
foreach (var e in Connectors)
{
ConnectorsGrids.Add(DrawConnector(new Thickness(e * YB1, 0, 0, 0)));
}
YB1 is the coefficient for the element's size. It is variable depending on the size of the screen
YB1 is not in any collection. Is a parameter in the class
DrawConnector() takes the margin as a parameter
I would like the margin to change depending on the size of the screen
At the moment, it calculates the output value and afterwards when changing the size of the screen, the changes remain
You can try this:
xaml:
<ItemsControl x:Name="PART_ItemsControl"
ItemsSource="{Binding Connectors}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Converter={local:ToGridConverter}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Converter:
sealed class ToGridConverter : MarkupExtension, IValueConverter{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
double e = (double)value;
return DrawConnector(new Thickness(e * YB1, 0, 0, 0));
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private ToGridConverter _converter;
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null)
{
_converter = new ToGridConverter ();
}
return _converter;
}}

WPF Multi-Binding / Aggregate Binding to Collection

I have a Control that I want to automatically disappear if another control has no visibile children. I'm not sure how to implement that though. I feel as though I need to create a binding that returns bindings for each child element's visible property and then aggregates them into a MultiValueConverter. I think it is working but it seems as though when I add items to my collection, the collection binding isn't being re-evaluated. Has anyone done this before?
Below is my code:
<Grid.Resources>
<local:BindingExpander x:Key="BindingExpander"/>
<local:TestConverter x:Key="TestConverter" />
</Grid.Resources>
<Button Content="Button" HorizontalAlignment="Left" Margin="237,166,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click">
<Button.Visibility>
<MultiBinding Converter="{StaticResource TestConverter}">
<Binding ElementName="lstItems" Path="Items" Converter="{StaticResource BindingExpander}" ConverterParameter="Visibility"/>
</MultiBinding>
</Button.Visibility>
</Button>
<ListBox x:Name="lstItems" HorizontalAlignment="Left" Height="100" Margin="601,130,0,0" VerticalAlignment="Top" Width="100" DisplayMemberPath="Content"/>
and:
public class TestConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
var ret = Visibility.Collapsed;
foreach (var item in values) {
if(item is IEnumerable IE) {
foreach (var Child in IE) {
}
}
}
return ret;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
public class BindingExpander : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var ret = new List<Binding>();
if(value is IEnumerable IE) {
foreach (var item in IE) {
ret.Add(new Binding(parameter.ToString()) {
Source = item,
Mode = BindingMode.OneWay
});
}
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
````
I have a Control that I want to automatically disappear if another
control has no visibile children..
Simply create a Boolean property which reports the status of what the other control is binding to such as:
public bool HasItems { get { return _SomeArray?.Any(); }}
This property can be as elaborate as needed, but a basic one above for the example is shown.
Then bind the visibility flag of the control in question to the HasItems.
Note that the HasItems does not have the plumbing for INotifyPropertyChanged. In the code(s) where items are added to the _SomeArray simply put in a call to PropertyChanged("HasItems")
On my blog I provide a basic example of that (Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding) which looks like this where someone would bind to IsMemebershipAtMax such as what you are doing:
public bool IsMembershipAtMax
{
get { return MemberCount > 3; }
}
public int MemberCount
{
get { return _MemberCount; }
set
{
_MemberCount = value;
OnPropertyChanged();
OnPropertyChanged("IsMembershipAtMax");
}
}
public List<string> Members
{
get { return _Members; }
set { _Members = value; OnPropertyChanged(); }
}

wpf - bind polyline to custom class

Does anyone know whether it is possible to bind a polyline to a collection of custom objects?
For example, I have a class like so:
public class MyDataClass{
public double Value { get; set; } //I'd like to map this to a polyline point's x value
public double Position { get; set; } //I'd like to map this to a polyline point's y value
}
And I'd like to bind a polyline to a collection of those objects and translate the Value property to X and the Position property to Y.
Thanks!
Although already answered by Joseph, I'd like to add a shorter and more flexible implementation of the Convert method, which uses the LINQ Select method:
using System.Linq;
...
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var myDataCollection = value as IEnumerable<MyDataClass>;
if (myDataCollection == null)
{
return null;
}
return new PointCollection(
myDataCollection.Select(p => new Point(p.Value, p.Position)));
}
The Polyline is expecting a PointCollection of Points in order to draw them, you could use a converter to assure that :
Xaml
<Polyline Stretch="Fill" Grid.Column="0"
Name="Polyline" Stroke="Red"
Points="{Binding Points,Converter={StaticResource ToPointConverter}}">
</Polyline>
the converter is implemented like so:
public class ToPointConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
var pointCollection=new PointCollection();
(value as List<MyDataClass>).ForEach(x=>{pointCollection.Add(new Point()
{
X = x.Value,
Y = x.Position
});});
return pointCollection;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and in your codebehind or your Viewmodel define the List<MyDataClass> property :
public List<MyDataClass> Points { get; set; }
don't forget to set the DataContext and set the ToPointConverter in your resource.
`

WPF converter and dictionary

How do you properly implement a IValueConverter if you have a dictionary populated in another class?
I'm sure i'm doing something wrong here but to explain my problem properly, here are the code supporting the question.
iPresenter_IconLists.cs
public interface iPresenter_IconLists
{
Dictionary<string, IconPositionDetails> IconDetails { get; set; }
}
Presenter_IconLists.cs
public class Presenter_IconLists : iPresenter_IconLists, IValueConverter
{
public Presenter_IconLists()
{
}
public void PopulateDictionaryTest()
{
this.IconDetails.Add("test1", new IconPositionDetails()
{
x = 0,
y = 0
});
this.IconDetails.Add("test2", new IconPositionDetails()
{
x = 0,
y = 0
});
this.IconDetails.Add("test3", new IconPositionDetails()
{
x = 0,
y = 0
});
}
Dictionary<string, IconPositionDetails> _IconDetails = new Dictionary<string, IconPositionDetails>();
public Dictionary<string, IconPositionDetails> IconDetails
{
get { return this._IconDetails; }
set { this._IconDetails = value; }
}
// IValueConverter implementation
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
iPresenter_IconLists i = this;
IconPositionDetails ipd = i.IconDetails[value.ToString()];
// or
// IconPositionDetails ipd = this.IconDetails[value.ToString()];
return string.Format("x: {0}, y: {1}", ipd.x, ipd.y);
}
}
MainWindow.xaml
<Window.Resources>
<l:Presenter_IconLists x:Key="DictConvert" x:Name="DictConvert" />
<TextBlock Text="{Binding Converter={StaticResource DictConvert}, ConverterParameter=Value.x}" Height="28" HorizontalAlignment="Left" Margin="60,49,0,0" VerticalAlignment="Top" FontSize="11" />
</Window.Resources>
MainWindow.xaml.cs
Presenter_IconLists iconlists;
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.iconlists = new Presenter_IconLists();
this.iconlists.PopulateDictionaryTest();
lbIcons.ItemsSource = this.iconlists.IconDetails;
}
the problem here is, I always getting "The given key was not present in the dictionary." and when I check the this.IconDetails number of collection, it was zero.
How do I access my dictionary inside the Converter?
Your constructor doesn't add items to the dictionary, so there are no items in it. When you go to convert, the dictionary is empty.
You need to, at some point, populate the dictionary with values. This is not happening, as the dictionary is created by WPF (<l:Presenter_IconLists x:Key="DictConvert" />) and never has values added to it.
you use converter Parameter instead of Path in binding.
use Path in binding:
<DataTemplate x:Key="lbItems" x:Name="lbItems">
<TextBlock Text="{Binding Path=Value.name, Converter={StaticResource DictConvert}}" />
</DataTemplate>
or use parameter in Convert function:
IconPositionDetails ipd = i.IconDetails[parameter.ToString()];

Path drawing and data binding

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>

Resources