Is there a marker bar component for a C# application what i could use in my application? As marker bar i mean something like ReSharper adds to Visual Studio:
Another example for something similar (the bar on the left):
EDIT: I found non-free component for java http://www.sideofsoftware.com/marker_bar/doc/sos/marker/JMarkerBar.html what does exactly what i want to do. It doesnt suite for me but maybe it helps someone.
In WPF the bar is a bit like a ListBox with just a different way of displaying a 1 pixel high line for each line of text. The state of the line would influence the color of the line and selecting a line would raise the SelectionChanged event to which the textbox could respond.
Let me know if you want me to show a prototype.
EDIT
Here goes. You can click/select a line in the bar and the textbox will scroll to that line.
Still to add:
what to do when the number of lines is to large for the bar?
A different way to show the line that is current in the bar?
Keep the selected line in the bar in sync with the caret in the text box.
...
These can be solved but depend largely on what you want. This should get you started.
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<local:StatusToBrushConverter x:Key="statusToBrushConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Opacity"
Value="0.5" />
<Setter Property="MaxHeight"
Value="1" />
<Setter Property="MinHeight"
Value="1" />
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Trigger.Setters>
<Setter Property="Opacity"
Value="1.0" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Rectangle StrokeThickness="0"
Stroke="Green"
Fill="{Binding Status, Converter={StaticResource statusToBrushConverter}}"
Height="1"
HorizontalAlignment="Stretch" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox AcceptsReturn="True"
Grid.Column="1"
x:Name="codeBox" />
</Grid>
</Window>
C#:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
private CodeLines lines;
public MainWindow()
{
InitializeComponent();
lines = new CodeLines();
Random random = new Random();
for (int i = 0; i < 200; i++)
{
lines.Add(new CodeLine { Status = (VersionStatus)random.Next(0, 5), Line = "Line " + i });
}
this.DataContext = lines;
codeBox.Text = String.Join("\n", from line in lines
select line.Line);
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedLine = ((ListBox)sender).SelectedIndex;
codeBox.ScrollToLine(selectedLine);
}
}
public enum VersionStatus
{
Original,
Added,
Modified,
Deleted
}
public class CodeLine : INotifyPropertyChanged
{
private VersionStatus status;
public VersionStatus Status
{
get { return status; }
set
{
if (status != value)
{
status = value;
OnPropertyChanged("Status");
}
}
}
private string line;
public string Line
{
get { return line; }
set
{
if (line != value)
{
line = value;
OnPropertyChanged("Line");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var p = PropertyChanged;
if (p != null)
{
p(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class CodeLines : ObservableCollection<CodeLine>
{
}
class StatusToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var status = (VersionStatus)value;
switch (status)
{
case VersionStatus.Original:
return Brushes.Green;
case VersionStatus.Added:
return Brushes.Blue;
case VersionStatus.Modified:
return Brushes.Yellow;
case VersionStatus.Deleted:
return Brushes.Red;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
You could use the Graphics class on a panel to paint it yourself.
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx
(I wouldn't use a bar graph as Teoman Soygul suggested, that's abusing components for something they aren't supposed to do. Same with the listbox idea by Erno. List boxes are made for showing lines of text, not marker bars.)
Related
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'm trying to create a new page in a WPF application. It is an "old" project, discovering the project and the language (I know C/C++, not C# nor XAML). In that page, I have to put a list of "rapport". But I can't refresh that list with new items, nor refresh the items.
I can't put that list in XAML, I have to use a C# variable. I try a lot of things, like List, observable list, refresh, binding, trigger event from expander, so a lot of code may be useless / unreachable.
And for a precision, piramidHandler.GetListParamReport(); has to be in the piramid class.
Thank's a lot for your help, pardon my English, still not my native language, but I try to make my best.
Rapports.xaml :
<Expander Header="{x:Static resx:StringResources.Lib_TabRapports}" VerticalAlignment="Top" Grid.Row="2" IsExpanded="False" IsEnabled="True" Expanded="ExpanderRapport">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--First row for controls-->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="650" />
<ColumnDefinition Width="650" />
</Grid.ColumnDefinitions>
</Grid>
<DataGrid ItemsSource="{Binding ListRapports}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserResizeColumns="False" Name="RapportGrid"
CanUserReorderColumns="False" Style="{StaticResource AzureDataGrid}" Grid.Row="1" FrozenColumnCount="2"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Auto">
<DataGrid.Resources>
<Style x:Key="AlignBottomColumnHeader" BasedOn="{StaticResource AzureDataGridColumnHeader}" TargetType="DataGridColumnHeader">
<Setter Property="VerticalContentAlignment" Value="Bottom" />
</Style>
<Style x:Key="RotatedColumnHeader" BasedOn="{StaticResource AzureDataGridColumnHeader}" TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="-4,0,0,0" />
<Setter Property="BorderBrush" Value="White" />
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-90" />
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
RapportViewModel.cs :
public sealed partial class Piramid
{
internal ObservableCollection<ViewModels.Rapport> listRapports;
public string GetRapportsList()
{
return piramidHandler.GetListParamReport();
}
public ObservableCollection<ViewModels.Rapport> ReloadRapportsLists()
{
var uniqueListRapport = new HashSet<ViewModels.Rapport>();
string gabaritsXML = GetRapportsList();
// [...] Fill uniqueListRapport
return new ObservableCollection<ViewModels.Rapport>(uniqueListRapport.ToList());
}
}
public class TabRapport : ViewModelBase
{
#region Properties
public TabRapport()
{
}
public ObservableCollection<ViewModels.Rapport> ListRapports
{
get
{
ObservableCollection<Rapport> test;
test = Piramid.Instance.ReloadRapportsLists();
if (test.Count == 0)
{
ViewModels.Rapport current = new ViewModels.Rapport
{
IdRapport = 52,
NameRapport = "This is a try"
};
test.Add(current);
ViewModels.Rapport current2 = new ViewModels.Rapport
{
IdRapport = 54,
NameRapport = "Second try"
};
test.Add(current2);
}
return test;
}
}
#endregion Properties
#region commands
// Useless in the context
#endregion commands
}
public class Rapport : ViewModelBase
{
public Rapport()
{
idRapport = -1;
}
public Rapport(Rapport e)
{
idRapport = e.IdRapport;
nameRapport = e.NameRapport;
}
private int idRapport;
private string nameRapport;
public int IdRapport
{
get => idRapport;
set => idRapport = value;
}
public string NameRapport
{
get => nameRapport;
set => nameRapport = value;
}
}
Rapports.xaml.cs :
public partial class Rapports : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
OnTargetUpdated();
}
private void ExpanderRapport(object sender, RoutedEventArgs args)
{
OnTargetUpdated();
}
public Rapports()
{
InitializeComponent();
Thread.CurrentThread.CurrentCulture = App.AppCultureInfo;
Thread.CurrentThread.CurrentUICulture = App.AppCultureInfo;
}
public void OnTargetUpdated()
{
RapportsRoot.Reload();
RapportGrid.Items.Refresh();
}
public void OnTargetUpdated(object sender, System.EventArgs e)
{
RapportsRoot.Reload();
RapportGrid.Items.Refresh();
}
}
EDIT :
DataContext : DataContext="{Binding Source={StaticResource Locator}, Path=OngletRapport}">
Reference to :
public TabRapport OngletRapport
{
get
{
return ServiceLocator.Current.GetInstance<TabRapport>();
}
}
EDIT 2 : Added code in the RapportViewModel.cs. My current try just show "This is a try" and "second try" as a list, not able to change it after setting data in listRapport
Okay, I finally find the answer.
I need to use ObservableObject.RaisePropertyChanged("ListRapports"); at the end of all background traitement, forcing the listRapports to refresh all data and items number.
Thank's a lot for all your help :)
sorry if you think this is a duplication of another post, but I tried every possible solution out there and I can't make it work.
The question is a two parter, but it's about the same code, so I thought I can ask the question in the same thread.
I try to do order system in C#, wpf, vs2015 using mvvm without any (hard coded) couplings. There is two things I need to do. First is to fire an event which I need to capture in the ViewModel, and second, when the number of articles is over a certain level, the background of the listboxitem for that article should be green (otherwise white/grey)
For this I use a ListBox and some listBoxItems.
MainWindow.xaml (the part that matters)
<Window x:Class="Sequence_Application_2.GUI.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:acb="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
mc:Ignorable="d"
....
<Window.DataContext>
<Grid Grid.Column="0" Margin="10">
<ListBox x:Name="LstViewFlows" SelectedItem="{Binding SelectedFlow.Name}" ItemsSource="{Binding Flows.Keys}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedFlow.CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="ListBoxItem.Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding SetSelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
Question 1)
I need to bind a command to a "MouseLeftButtonDown" event when I click on a listboxItem in a listbox. I tried the ACB-solution but can't get it to work. At last I tried the code above and I can't get it to trigger when I click on an item, but below the items, in the empty part of the listbox (not on a listboxitem), it tiggers as it should. Guess I need to rearange my xaml-file or something, but I tried for two days now I nothing seems to work.
The command is named "setSelectedCommand" and is implemented like this
internal class SetSelectedFlowCommand : ICommand
{
private FlowsViewModel _flowsViewModel;
public SetSelectedFlowCommand(FlowsViewModel flowsViewModel)
{
_flowsViewModel = flowsViewModel;
}
/// <summary>
///
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_flowsViewModel.SetSelectedFlow();
}
}
As I said, if I click on the ListBoxItem - nothing happends, but if i click in the ListBox - the command is fired (but nothing is "selected")
Question 2
As you see in the xaml above, I try to set the background color for a ListBoxitem, based on the value .CanDeliver in an object that is stored in a dictionary in the ViewModel. The variable Flow is the dictionary and SelectedFlow is supposed to be the flow that is selected.
This is an order system and CanDeliver is a variable that tells the operator/user if there is enough products to be delivered to the customer. If there are enough the listboxitem should be green, otherwise remain white/grey. Do you understand my question? Can I do it like this? Refering to an object inside a dictionary? (it's triggered by an INotifyPropertyChanged in the objects)
Hope you can help me because I have no more hair to pull from my head now ;-)
You don't need to use an event handler if all you want is to get the selectedItem. I have build an working example for you to show how to use binding only (MVVM) to achieve your requirements in your question:
C# (ViewModel):
using System;
using System.Collections.Generic;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MyViewModel mvm = new MyViewModel()
{
Flows = new ObservableCollection<Flow>()
{
new Flow() { Name = "Flow1" },
new Flow() { Name = "Flow2" },
new Flow() { Name = "Flow3" , Amount=1},
new Flow() { Name = "Flow4" }
}
};
this.DataContext = mvm;
}
}
public class MyViewModel : ObservableObject
{
private Flow _selectedflow;
public ObservableCollection<Flow> Flows
{
get;
set;
}
public Flow SelectedFlow
{
get { return _selectedflow; }
set
{
if (value != _selectedflow)
{
_selectedflow = value;
RaisePropertyChanged("SelectedFlow");
}
}
}
}
public class Flow : ObservableObject
{
private string _name;
private int _amount;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public bool CanDeliver
{
get
{
return Amount > 0;
}
}
public int Amount
{
get { return _amount; }
set
{
if (value != _amount)
{
_amount = value;
RaisePropertyChanged("Amount");
RaisePropertyChanged("CanDeliver");
}
}
}
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
protected void RaisePropertyChanged(String propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="350">
<Grid>
<StackPanel Orientation="Vertical">
<ListBox SelectedItem="{Binding SelectedFlow}" ItemsSource="{Binding Flows}" DisplayMemberPath="Name">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" >
<Style.Triggers>
<DataTrigger Binding="{Binding CanDeliver, UpdateSourceTrigger=PropertyChanged}" Value="true" >
<Setter Property="Background" Value="DarkGreen" />
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<TextBlock Text="{Binding SelectedFlow.Name}"/>
</StackPanel>
</Grid>
</Window>
Result: You can see the Flow item with CanDeliver = True (Amount>0) has a Green background. And the TextBlock is showing the SelectedFlow.
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!
I have a label control where I use a converter to switch its styles based on a bool property, IsCheckedOut on my viewmodel, like so:
<UserControl.Resources>
<Converters:BooleanStyleConverter
x:Key="BooleanStyleConverter "
StyleFalse="{StaticResource HeaderLabelStyle}"
StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
</UserControl.Resources>
<Label Style="{Binding Path=IsCheckedOut,
Converter={StaticResource BooleanStyleConverter}}">
some content here
</Label>
The converter simply returns one of the two styles:
public class BooleanStyleConverter : IValueConverter
{
public Style StyleFalse { get; set; }
public Style StyleTrue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return StyleTrue;
}
return StyleFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the styles look something like this:
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource RedGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource BlueGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So when IsCheckedOut is true the label gets a red background, and when it's false it gets a blue background (well, the styles are a bit more complicated, but you get the idea).
Now, I'd like to have a transition between the styles, so that the new colors fade in when IsCheckedOut changes.
Does anyone know how I can accomplish this?
Sorry, but you're doing it wrong.
You do get bonus points for being extremely creative and ambitious in your solution. But you've taken the proverbial 5kg hammer down on a thumbtack.
The correct solution in this situation is to use Storyboards nested in VSM States.
It looks like you essentially have 2 States for your UI: One where some business logic value is true and another state for when it's false. Note that the aforementioned distinction is 100% technology independent. In any technology, whatever it is you're trying to achieve would be considered 2 states for your UI.
In Silverlight/WPF, instead of hacking together something that mimics UI states, you could actually create VisualStateManager states.
Technically it would work in the following way:
1. Your UserControl would have 1 VisualStateGroup that has 2 VisualStates (one for true and another for false).
2. Those VSM states each represent 1 storyboard.
3. That storyboard would change the template or any other properties you feel are appropriate.
To learn the basics of VSM I strongly suggest you spend the next 30 minutes watching the following VSM videos: http://expression.microsoft.com/en-us/cc643423.aspx (Under "How Do I?")
Seriously, these videos are phenomenally successful in explaining VSM. The one that most pertinent to your dilemma is "Add States to a Control" but I'll suggest you watch all of them.
In WPF, you could use the VisualStateManager from the WPF Toolkit.
As Justin said, you are doing something wrong, and you might want to do five steps back and reconsider your approach...
But I really liked this puzzle :). I've solved it without VSM, just to demonstrate how flexible WPF is. Basic principle here is using Dependency properties value coercion. We track all style changes, but we don't let the new value go out from Coerce() function, until we complete transition animation.
To simplify your testing, just copy/paste the following code and check if it works for you :). If you want to get into details - feel free to ask additional questions.
XAML:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfApplication5"
Title="Fade Styles"
Width="320"
Height="240">
<Window.Resources>
<SolidColorBrush x:Key="RedGradient" Color="Red" />
<SolidColorBrush x:Key="BlueGradient" Color="Blue" />
<Style x:Key="HeaderLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource RedGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="HeaderLabelHighlightedStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Border Background="{StaticResource BlueGradient}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<loc:BooleanStyleConverter x:Key="BooleanStyleConverter"
StyleFalse="{StaticResource HeaderLabelStyle}"
StyleTrue="{StaticResource HeaderLabelHighlightedStyle}" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Style="{Binding IsChecked, ElementName=chkToggle, Converter={StaticResource BooleanStyleConverter}}"
loc:StyleAnimation.IsActive="True"
Content="Some content here" />
<CheckBox Grid.Row="1" Name="chkToggle" Content="Use Blue" />
</Grid>
</Window>
Take a look here on loc:StyleAnimation.IsActive="True".
C#
using System;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media.Animation;
namespace WpfApplication5
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
public class StyleAnimation : DependencyObject
{
private const int DURATION_MS = 200;
private static readonly Hashtable _hookedElements = new Hashtable();
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.RegisterAttached("IsActive",
typeof(bool),
typeof(StyleAnimation),
new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
public static bool GetIsActive(UIElement element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(IsActiveProperty);
}
public static void SetIsActive(UIElement element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(IsActiveProperty, value);
}
static StyleAnimation()
{
// You can specify any owner type, derived from FrameworkElement.
// For example if you want to animate style for every Control derived
// class - use Control. If Label is your single target - set it to label.
// But be aware: value coercion will be called every time your style is
// updated. So if you have performance problems, probably you should
// narrow owner type to your exact type.
FrameworkElement.StyleProperty.AddOwner(typeof(Control),
new FrameworkPropertyMetadata(
null, new PropertyChangedCallback(StyleChanged), CoerceStyle));
}
private static object CoerceStyle(DependencyObject d, object baseValue)
{
var c = d as Control;
if (c == null || c.Style == baseValue)
{
return baseValue;
}
if (CheckAndUpdateAnimationStartedFlag(c))
{
return baseValue;
}
// If we get here, it means we have to start our animation loop:
// 1. Hide control with old style.
// 2. When done set control's style to new one. This will reenter to this
// function, but will return baseValue, since CheckAndUpdateAnimationStartedFlag()
// will be true.
// 3. Show control with new style.
var showAnimation = new DoubleAnimation
{
Duration =
new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
To = 1
};
var hideAnimation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS)),
To = 0
};
hideAnimation.Completed += (o, e) =>
{
// To stress it one more: this will trigger value coercion again,
// but CheckAndUpdateAnimationStartedFlag() function will reture true
// this time, and we will not go to this loop again.
c.CoerceValue(FrameworkElement.StyleProperty);
c.BeginAnimation(UIElement.OpacityProperty, showAnimation);
};
c.BeginAnimation(UIElement.OpacityProperty, hideAnimation);
return c.Style; // Return old style this time.
}
private static void StyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// So what? Do nothing.
}
private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fe = d as FrameworkElement;
if (fe == null)
{
return;
}
if (GetIsActive(fe))
{
HookStyleChanges(fe);
}
else
{
UnHookStyleChanges(fe);
}
}
private static void UnHookStyleChanges(FrameworkElement fe)
{
if (_hookedElements.Contains(fe))
{
_hookedElements.Remove(fe);
}
}
private static void HookStyleChanges(FrameworkElement fe)
{
_hookedElements.Add(fe, false);
}
private static bool CheckAndUpdateAnimationStartedFlag(Control c)
{
var hookedElement = _hookedElements.Contains(c);
if (!hookedElement)
{
return true; // don't need to animate unhooked elements.
}
var animationStarted = (bool)_hookedElements[c];
_hookedElements[c] = !animationStarted;
return animationStarted;
}
}
public class BooleanStyleConverter : IValueConverter
{
public Style StyleFalse { get; set; }
public Style StyleTrue { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
{
return StyleTrue;
}
return StyleFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Cheers :)
I too got a solution becuase I as well liked your problem :)
I solved the puzzle using ColorAnimations on the gradient. Have a look:
<Window x:Class="WpfTest___App.DoEvents.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" MinHeight="300" MinWidth="300"
Name="Window">
<Grid DataContext="{Binding ElementName=Window}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="Some content here" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Label.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="Blue" Offset="0"/>
<GradientStop Color="LightBlue" Offset="0.4"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Label.Background>
<Label.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsCheckedOut}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color"
To="Red" Duration="0:0:5"/>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color"
To="Orange" Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[0].Color"
To="Blue" Duration="0:0:5"/>
<ColorAnimation Storyboard.TargetProperty="Background.GradientStops[1].Color"
To="LightBlue" Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Button Content="Change IsCheckedOut" Click="Button_Click" Grid.Row="1"/>
</Grid>
In the code behind I created a Dependency property for testing and a listener to the Click event of the button:
public partial class Window1 : Window
{
public bool IsCheckedOut
{
get { return (bool)GetValue(IsCheckedOutProperty); }
set { SetValue(IsCheckedOutProperty, value); }
}
public static readonly DependencyProperty IsCheckedOutProperty = DependencyProperty.Register("IsCheckedOut", typeof(bool), typeof(Window1), new PropertyMetadata(false));
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
IsCheckedOut = !IsCheckedOut;
}
}
This should solve your problem as well :)