I am trying to draw a polyline on a canvas which will have rectangle on each point. The Polyline is bound to a collection of points from the ViewModel.
When I try to set DataTemplate for each point (like below) it shows no rectangle on polyline points.
Is there some way to display rectangle on polyline points?
Later I want to adjust the polyline by dragging these points.
<Polyline Points="{Binding EdgePoints, Converter={StaticResource pointCollectionConverter}}" StrokeThickness="2">
<Polyline.Resources>
<DataTemplate DataType="{x:Type Point}">
<Rectangle Width="20" Height="20" Fill="Black"/>
</DataTemplate>
</Polyline.Resources>
</Polyline>
Here is example where I want to draw rectangles.
You could have a view model like the one shown below. Besides the obvious parts, it attaches/detaches a PropertyChanged handler to/from each Vertex in order to fire the PropertyChanged event for the Vertices property. This is necessary to update the Polyline's Point Binding.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Vertex : ViewModelBase
{
private Point point;
public Point Point
{
get { return point; }
set { point = value; OnPropertyChanged(); }
}
}
public class ViewModel : ViewModelBase
{
public ViewModel()
{
Vertices.CollectionChanged += VerticesCollectionChanged;
}
public ObservableCollection<Vertex> Vertices { get; }
= new ObservableCollection<Vertex>();
private void VerticesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged += VertexPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems.OfType<INotifyPropertyChanged>())
{
item.PropertyChanged -= VertexPropertyChanged;
}
}
OnPropertyChanged(nameof(Vertices));
}
private void VertexPropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(Vertices));
}
}
The Vertices to PointCollection converter could look like this:
public class VerticesConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var vertices = value as IEnumerable<Vertex>;
return vertices != null
? new PointCollection(vertices.Select(v => v.Point))
: null;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The view would use a Polyline and an ItemsControl. The ItemsTemplate would declare a Thumb element that handles the dragging of vertex points.
<Canvas>
<Canvas.Resources>
<local:VerticesConverter x:Key="VerticesConverter"/>
<Style x:Key="ThumbStyle" TargetType="Thumb">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Rectangle Fill="Transparent" Stroke="Red"
Width="10" Height="10" Margin="-5,-5"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<EventSetter Event="DragDelta" Handler="ThumbDragDelta"/>
</Style>
</Canvas.Resources>
<Polyline Points="{Binding Vertices, Converter={StaticResource VerticesConverter}}"
Stroke="DarkBlue" StrokeThickness="3" StrokeLineJoin="Round"/>
<ItemsControl ItemsSource="{Binding Vertices}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Point.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Point.Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Thumb Style="{StaticResource ThumbStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
Finally, the Thumb's DragDelta handler:
private void ThumbDragDelta(object sender, DragDeltaEventArgs e)
{
var vertex = (Vertex)((Thumb)sender).DataContext;
vertex.Point = new Point(
vertex.Point.X + e.HorizontalChange,
vertex.Point.Y + e.VerticalChange);
}
Related
When trying to change background of TextBlock that is part of a DataTemplate of a ListBox the background is only around the text and not the entire block
In UWP TextBlock doesn't have background property so I've wrapped it in a border and changed the border's background like this:
<ListBox x:Name="BitsListView" ItemsSource="{x:Bind BitsList, Mode=TwoWay}" Loaded="BitsListView_Loaded"
HorizontalAlignment="Left" IsEnabled="{x:Bind IsWriteAccess,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="BitsListView_SelectionChanged " SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="BitText" Text="{Binding}" Loaded="BitText_Loaded" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
And the color is changed in an OnLoaded event like this:
private void BitText_Loaded(object sender, RoutedEventArgs e)
{
TextBlock bitText = sender as TextBlock;
StackPanel sp = bitText.Parent as StackPanel;
Border border = sp.Parent as Border;
if ((int)bitText.DataContext == 1)
{
bitText.Foreground = new SolidColorBrush(Windows.UI.Colors.LightGreen);
border.Background = new SolidColorBrush(Windows.UI.Colors.DarkGreen);
}
else
{
bitText.Foreground = new SolidColorBrush(Windows.UI.Colors.Gray);
border.Background = new SolidColorBrush(Windows.UI.Colors.LightGray);
}
}
But the result is this:
https://pasteboard.co/IlcZB1J.png
What i'm trying to achive is something like this:
(Don't mind the bad MSPaint job)
https://pasteboard.co/Ild1plp.png
What i've tried to do to solve this is wrapping the stackpanel with border, but that didnt help.
then i've tried to wrap the datatemplate but that is not possible, climbing further up the tree changing the backgrounds is not working properly, and obviously changing the ListBox's background paints the entire list, and I need only the blocks that has 1 to be painted fully and not just a little bit around the text
For your question, you do not need to use border to wrap the StackPanel, it will not work. You just need to define a style for ListBoxItem and apply it to ItemContainerStyle and set HorizontalContentAlignment=Stretch.
Here, I checked your code. I have some suggestions for you. In UWP, you could do most things with binding. That means you do not need to find the specific control and set its property value from DataTemplate in the page's code-behind. It's not best practice. Instead, you could define a custom class which includes three properties(text,background,foreground). Then, you could bind to these properties on your XAML page.
The complete code sample like the following:
<ListView x:Name="BitsListView" ItemsSource="{x:Bind BitsList, Mode=TwoWay}"
HorizontalAlignment="Left" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="VerticalContentAlignment" Value="Stretch"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="Padding" Value="0"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" ></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Test">
<StackPanel Background="{x:Bind backGround}">
<TextBlock x:Name="BitText" Text="{x:Bind content}" Foreground="{x:Bind foreGround}" HorizontalTextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class Test : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _content;
public int content
{
get { return _content; }
set
{
if (_content != value)
{
_content = value;
if (value == 1)
{
foreGround = new SolidColorBrush(Colors.LightGreen);
backGround = new SolidColorBrush(Colors.DarkGreen);
}
else
{
foreGround = new SolidColorBrush(Colors.Gray);
backGround = new SolidColorBrush(Colors.LightGray);
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("content"));
}
}
}
private SolidColorBrush _backGround;
public SolidColorBrush backGround
{
get { return _backGround; }
set
{
if (_backGround != value)
{
_backGround = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("backGround"));
}
}
}
private SolidColorBrush _foreGround;
public SolidColorBrush foreGround
{
get { return _foreGround; }
set
{
if (_foreGround != value)
{
_foreGround = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("foreGround"));
}
}
}
}
public sealed partial class MainPage : Page
{
private ObservableCollection<Test> BitsList { get; set; }
public MainPage()
{
this.InitializeComponent();
BitsList = new ObservableCollection<Test>();
for (int i = 0; i < 10; i++)
{
Random random = new Random();
BitsList.Add(new Test() { content = random.Next(0, 9) });
}
}
}
I'm working towards making click and drag-able spline curves while learning WPF. I've been able to successfully work with pure Line segments, but making the jump to a polyline is proving difficult. I have a class for interpolating the spline curves that I used to use in WinForms, so I'm using a few input clicks from the mouse, and those will be the thumbs to click and drag. The interpolated points have a high enough resolution that a WPF Polyline should be fine for display. To clarify, I need the higher resolution output, so using a WPF Beizer is not going to work.
I have the outline pretty well setup- but the particular issue I'm having, is that dragging the thumbs does not either a) the two way binding is not setup correctly, or b) the ObservableCollection is not generating notifications. I realize that the ObservableCollection only notifies when items are added/removed/cleared, etc, and not that the individual indices are able to produce notifications. I have spent the last few hours searching- found some promising ideas, but haven't been able to wire them up correctly. There was some code posted to try inherit from ObservableCollection and override the OnPropertyChanged method in the ObservableCollection, but that's a protected virtual method. While others used a method call into the OC to attach PropertyChanged event handlers to each object, but I'm unsure where to inject that logic. So I am a little stuck.
MainWindow.xaml:
There is an ItemsControl hosted in a mainCanvas. ItemsControl is bound to a property on the ViewModel
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu>
<MenuItem x:Name="menuAddNewPolyline" Header="Add Polyline" Click="MenuItem_Click" />
</Menu>
<Canvas x:Name="mainCanvas" Grid.Row="1">
<ItemsControl x:Name="polylinesItemsControl"
ItemsSource="{Binding polylines}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
MainWindow.Xaml.cs:
Pretty simple- initializes a new view model, and it's set as the DataContext. There is a menu with a Add Polyline item, which in turn, initializes a new PolylineControl, and generates three random points (using Thread.Sleep, otherwise they were the same, between the calls) within the ActualHeight and ActualWidth of the window. The new PolylineControl is added to the ViewModel in an ObservableCollection This is a stand in until I get to accepting mouse input.
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
DataContext = viewModel;
}
private Point GetRandomPoint()
{
Random r = new Random();
return new Point(r.Next(0, (int)this.ActualWidth), r.Next(0, (int)this.ActualHeight));
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var newPolyline = new PolylineControl.Polyline();
newPolyline.PolylinePoints.Add(GetRandomPoint());
Thread.Sleep(100);
newPolyline.PolylinePoints.Add(GetRandomPoint());
Thread.Sleep(100);
newPolyline.PolylinePoints.Add(GetRandomPoint());
viewModel.polylines.Add(newPolyline);
}
}
ViewModel.cs:
Absolutely noting fancy here
public class ViewModel
{
public ObservableCollection<PolylineControl.Polyline> polylines { get; set; }
public ViewModel()
{
polylines = new ObservableCollection<PolylineControl.Polyline>();
}
}
**The PolylineControl:
Polyline.cs:**
Contains DP's for an ObservableCollection of points for the polyline. Eventually this will also contain the interpolated points as well as the input points, but a single collection of points will do for the demo. I did try to use the INotifyPropertyChanged interface to no avail.
public class Polyline : Control
{
public static readonly DependencyProperty PolylinePointsProperty =
DependencyProperty.Register("PolylinePoints", typeof(ObservableCollection<Point>), typeof(Polyline),
new FrameworkPropertyMetadata(new ObservableCollection<Point>()));
public ObservableCollection<Point> PolylinePoints
{
get { return (ObservableCollection<Point>)GetValue(PolylinePointsProperty); }
set { SetValue(PolylinePointsProperty, value); }
}
static Polyline()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Polyline), new FrameworkPropertyMetadata(typeof(Polyline)));
}
}
Generic.xaml
Contains a canvas with a databound Polyline, and an ItemsControl with a DataTemplate for the ThumbPoint control.
<local:PointCollectionConverter x:Key="PointsConverter"/>
<Style TargetType="{x:Type local:Polyline}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Polyline}">
<Canvas Background="Transparent">
<Polyline x:Name="PART_Polyline"
Stroke="Black"
StrokeThickness="2"
Points="{Binding Path=PolylinePoints,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource PointsConverter}}"
>
</Polyline>
<ItemsControl x:Name="thumbPoints"
ItemsSource="{Binding PolylinePoints, RelativeSource={RelativeSource TemplatedParent}}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<tc:ThumbPoint Point="{Binding Path=., Mode=TwoWay}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
PointsCollectionConverter.cs:
Contains a IValueConverter to turn the ObservableCollection into a PointsCollection.
public class PointCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() == typeof(ObservableCollection<Point>) && targetType == typeof(PointCollection))
{
var pointCollection = new PointCollection();
foreach (var point in value as ObservableCollection<Point>)
{
pointCollection.Add(point);
}
return pointCollection;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
And finally, the ThumbPointControl:
ThumbPoint.cs:
Contains a single DP for the center of the point, along with the DragDelta functionality.
public class ThumbPoint : Thumb
{
public static readonly DependencyProperty PointProperty =
DependencyProperty.Register("Point", typeof(Point), typeof(ThumbPoint),
new FrameworkPropertyMetadata(new Point()));
public Point Point
{
get { return (Point)GetValue(PointProperty); }
set { SetValue(PointProperty, value); }
}
static ThumbPoint()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ThumbPoint), new FrameworkPropertyMetadata(typeof(ThumbPoint)));
}
public ThumbPoint()
{
this.DragDelta += new DragDeltaEventHandler(this.OnDragDelta);
}
private void OnDragDelta(object sender, DragDeltaEventArgs e)
{
this.Point = new Point(this.Point.X + e.HorizontalChange, this.Point.Y + e.VerticalChange);
}
}
Generic.xaml:
Contains the style, and an Ellipse bound which is databound.
<Style TargetType="{x:Type local:ThumbPoint}">
<Setter Property="Width" Value="8"/>
<Setter Property="Height" Value="8"/>
<Setter Property="Margin" Value="-4"/>
<Setter Property="Background" Value="Gray" />
<Setter Property="Canvas.Left" Value="{Binding Path=Point.X, RelativeSource={RelativeSource Self}}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Point.Y, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ThumbPoint}">
<Ellipse x:Name="PART_Ellipse"
Fill="{TemplateBinding Background}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Window after the Add Polyline menu item is pressed
The code works to add the polyline with three random points.
Thumbs moved away from poly line
However, once you move the thumbs, the polyline does not update along with it.
I have a working example of just a single line segment (added to the view model as many times as you click the add segment button) so it seems the logic should all be correct, but something broke down with the introduction of the ObservableCollection to host the multiple points required for a polyline.
Any help is appreciated
Following on from Clemens suggestions, I was able to make it work.
I renamed the Polyline.cs control to eliminate confusion with the standard WPF Polyline Shape class to DynamicPolyline. The class now implements INotifyPropertyChanged, and has DP for the PolylinePoints and a seperate ObservableCollection for a NotifyingPoint class which also implements INotifyPropertyChanged. When DynamicPolyline is initialized, it hooks the CollectionChanged event on the ObserableCollection. The event handler method then either adds an event handler to each item in the collection, or removes it based on the action. The event handler for each item simply calls SetPolyline, which in turn cycles through the InputPoints adding them to a new PointCollection, and then sets the Points property on the PART_Polyline (which a reference to is created in the OnApplyTemplate method).
It turns out the Points property on a Polyline does not listen to the INotifyPropertyChanged interface, so data binding in the Xaml was not possible. Probably will end up using a PathGeometery in the future, but for now, this works.
To address Marks non MVVM concerns.. It's a demo app, sorry I had some code to test things in the code behind. The point is to be able to reuse these controls, and group them with others for various use cases, so it makes more sense for them to be on their own vs repeating the code.
DynmicPolyline.cs:
public class DynamicPolyline : Control, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public static readonly DependencyProperty PolylinePointsProperty =
DependencyProperty.Register("PoilylinePoints", typeof(PointCollection), typeof(DynamicPolyline),
new PropertyMetadata(new PointCollection()));
public PointCollection PolylinePoints
{
get { return (PointCollection)GetValue(PolylinePointsProperty); }
set { SetValue(PolylinePointsProperty, value); }
}
private ObservableCollection<NotifyingPoint> _inputPoints;
public ObservableCollection<NotifyingPoint> InputPoints
{
get { return _inputPoints; }
set
{
_inputPoints = value;
OnPropertyChanged();
}
}
private void SetPolyline()
{
if (polyLine != null && InputPoints.Count >= 2)
{
var newCollection = new PointCollection();
foreach (var point in InputPoints)
{
newCollection.Add(new Point(point.X, point.Y));
}
polyLine.Points = newCollection;
}
}
private void InputPoints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
var point = item as NotifyingPoint;
point.PropertyChanged += InputPoints_PropertyChange;
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var item in e.OldItems)
{
var point = item as NotifyingPoint;
point.PropertyChanged -= InputPoints_PropertyChange;
}
}
}
private void InputPoints_PropertyChange(object sender, PropertyChangedEventArgs e)
{
SetPolyline();
}
public DynamicPolyline()
{
InputPoints = new ObservableCollection<NotifyingPoint>();
InputPoints.CollectionChanged += InputPoints_CollectionChanged;
SetPolyline();
}
static DynamicPolyline()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicPolyline), new FrameworkPropertyMetadata(typeof(DynamicPolyline)));
}
private Polyline polyLine;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
polyLine = this.Template.FindName("PART_Polyline", this) as Polyline;
}
NotifyingPoint.cs
Simple class that raises property changed events when X or Y is updated from the databound ThumbPoint.
public class NotifyingPoint : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public event EventHandler ValueChanged;
private double _x = 0.0;
public double X
{
get { return _x; }
set
{
_x = value;
OnPropertyChanged();
ValueChanged?.Invoke(this, null);
}
}
private double _y = 0.0;
public double Y
{
get { return _y; }
set
{
_y = value;
OnPropertyChanged();
}
}
public NotifyingPoint()
{
}
public NotifyingPoint(double x, double y)
{
X = x;
Y = y;
}
public Point ToPoint()
{
return new Point(_x, _y);
}
}
And finally, for completeness, here is the Generic.xaml for the control. Only change in here was the bindings for X and Y of the NotifyingPoint.
<Style TargetType="{x:Type local:DynamicPolyline}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DynamicPolyline}">
<Canvas x:Name="PART_Canvas">
<Polyline x:Name="PART_Polyline"
Stroke="Black"
StrokeThickness="2"
/>
<ItemsControl x:Name="PART_ThumbPointItemsControl"
ItemsSource="{Binding Path=InputPoints, RelativeSource={RelativeSource TemplatedParent}}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<tc:ThumbPoint X="{Binding Path=X, Mode=TwoWay}" Y="{Binding Path=Y, Mode=TwoWay}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I dropped my Spline class in to the SetPolyline method, and got the result I was after:
Two working click and drag able spline curves
i am making a simple list with few options.
This is how it looks like:
Buttons appear when hit the listviewitem and disappear when leave
When press Play, then this button stays Visible and Content changes to Stop
My problems is:
When i press Stop, then this Button stays Visible and Triggers disappear :/
What else i want to do, but i can't is:
When i press Play then Slider appears, otherwise Collapsed
I hope that someone can help me.
My code looks like this so far:
XAML:
<ListView Name="lst">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Name="btnDownload" Content="Download" Visibility="Hidden" MinWidth="100"/>
<Button Name="btnPlay"
Click="btnPlay_Click"
Content="Play"
Visibility="Hidden"
MinWidth="100"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}},
Path=IsMouseOver}"
Value="True">
<Setter TargetName="btnDownload"
Property="Visibility"
Value="Visible"/>
<Setter TargetName="btnPlay"
Property="Visibility"
Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Name">Name</GridViewColumnHeader>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel MinWidth="200">
<TextBlock Text="{Binding Name}"/>
<Slider Name="Slider" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
CS:
public partial class MainWindow : Window
{
public ListCollectionView MyCollectionView { get; set; }
public ObservableCollection<Songs> songs = new ObservableCollection<Songs>();
public MainWindow()
{
InitializeComponent();
MyCollectionView = new ListCollectionView(songs);
lst.ItemsSource = MyCollectionView;
songs.Add(new Songs(){Name = "Eminem - Superman"});
songs.Add(new Songs(){Name = "Rihanna - Please don't stop the music"});
songs.Add(new Songs(){Name = "Linkin Park - Numb"});
}
private void btnPlay_Click(object sender, RoutedEventArgs e)
{
//Reset all songs
List<Button> buttons = FindVisualChildren<Button>(lst).ToList();
foreach (Button button in buttons)
{
button.Content = "Play";
//Loosing Triggers
}
//Play current
Button btn = sender as Button;
btn.Visibility = Visibility.Visible;
btn.Content = "Stop";
}
private IEnumerable<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
{
yield return (T)child;
}
else
{
var childOfChild = FindVisualChildren<T>(child);
if (childOfChild != null)
{
foreach (var subchild in childOfChild)
{
yield return subchild;
}
}
}
}
}
}
public class Songs : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Project:
MusicList.sln
And ofcourse - sorry for my bad english :)
You could create a value converter that would take the text of your button and return a visibility value based on the text value. Something like this;
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var buttonText = (string) value;
switch (buttonText.ToLower())
{
case "stop":
return Visibility.Visible;
default:
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Create an instance of this class in your xaml by creating a static resource and adding it into your resource dictionary e.g. something like this;
<Window.Resources>
<myNamespace:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
</Window.Resources>
Then bind your slider visibility to your button text;
<Slider Name="Slider" Visibility="{Binding ElementName=btnPlay, Path=Content, Converter={StaticResource StringToVisibilityConverter}}"/>
I have the following ItemsControl, as shown it has hard-coded values, I would like to shift these values into an attached property, probably an ObservableCollection or something similar.
How to create this attached property and how to bind it.
<ItemsControl Grid.Column="0" VerticalAlignment="Stretch" Name="ItemsSelected">
<sys:Double>30</sys:Double>
<sys:Double>70</sys:Double>
<sys:Double>120</sys:Double>
<sys:Double>170</sys:Double>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="SlateGray" Width="18" Height="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Top" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
[EDIT]
So I think I have the attached property figured:
public static class ScrollBarMarkers
{
public static readonly DependencyProperty MarkersSelectedCollectionProperty =
DependencyProperty.RegisterAttached("MarkersSelectedCollection", typeof(ObservableCollection<double>), typeof(ScrollBarMarkers), new PropertyMetadata(null));
public static ObservableCollection<double> GetMarkersSelectedCollection(DependencyObject obj)
{
return (ObservableCollection<double>)obj.GetValue(MarkersSelectedCollectionProperty);
}
public static void SetMarkersSelectedCollection(ItemsControl obj, ObservableCollection<double> value)
{
obj.SetValue(MarkersSelectedCollectionProperty, value);
}
}
What I'm wondering now is the best way to get the ItemsControl object before calling the following in the selection changed behavior:
ScrollBarMarkers.SetMarkersSelectedCollection(ItemsControl, initSelected);
The style of the customized vertical scrollbar is setup in the Window.Resources
The behavior is set up on the DataGrid like so:
<DataGrid Name="GenericDataGrid">
<i:Interaction.Behaviors>
<helpers:DataGridSelectionChanged />
</i:Interaction.Behaviors>
</DataGrid>
My selection changed behavior:
public class DataGridSelectionChanged : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += DataGrid_SelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= DataGrid_SelectionChanged;
}
void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ObservableCollection<double> initSelected = new ObservableCollection<double>();
initSelected.Add(30);
initSelected.Add(60);
initSelected.Add(100);
//Just trying to figure out how best to get the ItemsControl object.
ScrollBarMarkers.SetMarkersSelectedCollection(itemsControlObj, initSelected);
}
}
Below is an example of the markers in the scrollbar, a ItemsControl has been added to the custom vertical scrollbar as per the code right at the top of the question.
If I understand your question, you want bind an ObservableCollection to ItemsControl and when the items are long the scrollbar will appear.
This solution could serve you.
[I will working with MVVM]
You can create a ObservableCollection in your code.
private ObservableCollection<int> _list = new ObservableCollection<int>();
public ObservableCollection<int> List
{
get { return _list ; }
set { _list = value; RaisePropertyChanged("List"); }
}
Now, you binding Collection to ItemsControl
<ScrollViewer HorizontalAlignment="Left" Width="254" Height="Auto" >
<ItemsControl x:Name="ItemsControlComputers" ItemsSource="{Binding List, Mode=TwoWay}" Height="Auto"
HorizontalAlignment="Left" Width="254" ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
Background="{x:Null}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="254">
<TextBlock Text="{Binding }" Margin="4,0,0,5" VerticalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Went down the wrong track with this instead of creating a DependencyProperty I should have just created a plain property, however because it is UI related I did not want it with my ViewModel. So I created a class with singleton pattern in the same namespace as my behavior and other attached properties. This also means I can set the collection from any behaviors.
Here is the binding:
<ItemsControl ItemsSource="{Binding Source={x:Static helpers:MyClass.Instance}, Path=SelectedMarkers}">
Here is the class with singleton pattern
public class MyClass : INotifyPropertyChanged
{
public static ObservableCollection<double> m_selectedMarkers = new ObservableCollection<double>();
public ObservableCollection<double> SelectedMarkers
{
get
{
return m_selectedMarkers;
}
set
{
m_selectedMarkers = value;
NotifyPropertyChanged();
}
}
private static MyClass m_Instance;
public static MyClass Instance
{
get
{
if (m_Instance == null)
{
m_Instance = new MyClass();
}
return m_Instance;
}
}
private MyClass()
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I have a task of programming a simple program-demonstration of the Lee Algorithm for pathfinding in a maze. I want to make it somewhat graphically interactive: create a 2D table with a variable amount of rows and columns, which's cells can be clicked (and the position of the clicked cell should be able to be tracked). This is because I want to let the user draw the maze obstacles, set the start point etc. What would be the best graphical component that could help me do this and how can I interact with its' cells?
Following on from your comment I would say that WPF is a more natural candidate for this task because it has been designed to do custom layout stuff. Here is a code example I've put together using an items control with a uniform grid that displays a grid of cells - clicking on a cell selects it, clicking again deselects it.
This isn't great WPF, but it might give you some ideas and get you started.
Application:
XAML:
<Window x:Class="LeeAlgorithm.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="380" Width="350">
<Grid>
<ItemsControl ItemsSource="{Binding Cells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="18"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataTemplate.Resources>
<Grid Width="30" Height="30" Margin="2">
<DockPanel ZIndex="9999">
<!-- This is a hack to capture the mouse click in the area of the item -->
<Rectangle
Fill="Transparent"
DockPanel.Dock="Top"
MouseDown="UIElement_OnMouseDown"
Tag="{Binding}" />
</DockPanel>
<Grid>
<Rectangle Fill="{Binding Path=Color}" Stroke="Red" StrokeDashArray="1 2" />
<TextBlock Margin="3,3,3,0" Text="{Binding Path=CellNumber}"/>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code Behind (your MainWindow.xaml.cs code):
namespace LeeAlgorithm
{
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.Cells = new ObservableCollection<Cell>();
for (int i = 0; i != 50; ++i)
{
this.Cells.Add(new Cell { CellNumber = i });
}
InitializeComponent();
DataContext = this;
}
public ObservableCollection<Cell> Cells { get; private set; }
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var cell = (Cell)((Rectangle)sender).Tag;
if (!cell.IsSelected)
{
cell.Color = new SolidColorBrush(Colors.HotPink);
cell.IsSelected = true;
}
else
{
cell.Color = new SolidColorBrush(Colors.Silver);
cell.IsSelected = false;
}
}
}
public class Cell : INotifyPropertyChanged
{
private int cellNumber;
private SolidColorBrush color = new SolidColorBrush(Colors.Silver);
public event PropertyChangedEventHandler PropertyChanged;
public int CellNumber
{
get
{
return this.cellNumber;
}
set
{
if (value == this.cellNumber)
{
return;
}
this.cellNumber = value;
this.OnPropertyChanged("CellNumber");
}
}
public SolidColorBrush Color
{
get
{
return this.color;
}
set
{
if (Equals(value, this.color))
{
return;
}
this.color = value;
this.OnPropertyChanged("Color");
}
}
public bool IsSelected { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Happy coding!