drawing new lines in an already populated canvas - wpf

So I got on my grid a canvas which as its background it get a drawing brush from resource dictionary.That not a problem it works perfect …
But now I need to draw an open ended polygon on it , which gets its coordinates as an array of X and Y. let’s say intX[i] and intY[i] arrays…. So point 1 is intX[0] and intY[0] and so on ….
And after that depending on some calculation I will get some more attributes and I need to add some horizontals and vertical lines ‘no more polygons’ .
After that I need to write the result on it … so on point (x1,y1) I need to WRITE the result
Ps : is this even possible …. Is canvas a good choice ‘ I chose canvas because the coordinates should be absolute and doesn’t change when window or object is resized’ the newly drawn lines should not delete the previous drawn line or the background’
Sorry or my bad English and I hope u can guide me with something to start with….

Not sure I fully understand your need but you could use a custom ItemsControl.
First you need an entity to store all the infos for a single data-point, its position and label:
public class DataPoint : INotifyPropertyChanged
{
private double left;
public double Left
{
get { return left; }
set
{
if (value != left)
{
left = value;
PropertyChanged(this, new PropertyChangedEventArgs("Left"));
}
}
}
private double top;
public double Top
{
get { return top; }
set
{
if (value != top)
{
top = value;
PropertyChanged(this, new PropertyChangedEventArgs("Top"));
}
}
}
private string text;
public string Text
{
get { return text; }
set
{
if (value != text)
{
text = value;
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
Then in your VM or directly in your code-behind you could expose it with an ObservableCollection:
public ObservableCollection<DataPoint> Points { get; set; }
Then on the view side:
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center">
<Button Click="GetData_Click">Get Data</Button>
<Button Click="GetText_Click">Get Text</Button>
</StackPanel>
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.Template>
<ControlTemplate>
<Canvas IsItemsHost="True" Background="AliceBlue" Width="500" Height="500"></Canvas>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:DataPoint}">
<Canvas>
<Ellipse Fill="Red" Width="6" Height="6" Canvas.Left="-3" Canvas.Top="-3"></Ellipse>
<TextBlock Text="{Binding Text}" Canvas.Left="10" Canvas.Top="0"></TextBlock>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DockPanel>
local is the mapping of the DataPoint class namespace.
Here are the two event handlers used for demo purposes:
Random rand = new Random();
private void GetData_Click(object sender, RoutedEventArgs e)
{
const int max = 500;
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
Points.Add(new DataPoint { Left = rand.Next(max), Top = rand.Next(max) });
}
private void GetText_Click(object sender, RoutedEventArgs e)
{
foreach (DataPoint p in Points)
{
p.Text = ((char)('a' + rand.Next(26))).ToString();
}
}
Hope this helps.

Related

WPF datagrid - Group by datagrid with editable column reoders rows in group when editable cell is "opened" or edited

I am facing a strange problem in WPF datagrid after grouping. The rows inside groups start reordering. I am using the .net 3.5 framework's datagrid from codeplex.
The problem has been asked in msdn forum. Please find the link below.
http://social.msdn.microsoft.com/Forums/en/wpf/thread/3f915bf9-2571-43e8-8560-3def1d0d65bb
The msdn user has says at the end of the thread that there might be no workarounds. But I badly need one !!
Have you tried doing your sorting via a CustomSort? I saw this posted somewhere and it worked for me. It took a little bit of finding, but it's available via the ListCollectionView class. So something like:
ListCollectionView lcv =
(ListCollectionView)CollectionViewSource.GetDefaultView(YourObjectCollection);
lcv.CustomSort = new YourObjectComparer();
Where YourObjectComparer implements IComparer and sorts on the properties that you want to.
EDIT:
For this when a cell edit takes place, we remove groups and add them again. This cuases the cell to stay on its intended order. But this poses another issue that is all the expanders are collapsed. So if we remember which expander(s) remains expanded after regrouping, we achieve what you seek.
There are a few events which can help you with achieving this functionality...
DataGrid.CellEditEnding event
Expander.Initialized, Expander.Expanded and Expander.Collpased events
ICollectionView.CurrentChanging event
All you need to remember is the status of the expanders when they are expanded or collapsed. Each expander represents the grouped value represented by Name property. These grouped values are unique. So a dictionary where Dictionary.Key is this Name value and Dictionary.Value is the Expander.IsExpanded flag would just suffice.
With this raw material following code does what you seek...
Model Class: I represent a simple list of Key-Value objects in the DataGrid.
public class MyKeyValuePair<TKey, TValue> : INotifyPropertyChanged
{
private TKey key;
private TValue value;
public MyKeyValuePair(TKey k, TValue v)
{
key = k;
value = v;
}
public TKey Key
{
get { return key; }
set {
key = value;
OnPropertyChanged("Key");
}
}
public TValue Value
{
get { return value; }
set {
this.value = value;
OnPropertyChanged("Value");
}
}
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged
(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
XAML:
<tk:DataGrid
ItemsSource="{Binding}"
CellEditEnding="MyDataGrid_CellEditEnding">
<tk:DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander
Initialized="Expander_Initialized"
Expanded="Expander_Expanded"
Collapsed="Expander_Expanded">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock
Text="{Binding Path=Name}" />
<TextBlock Text=" (" />
<TextBlock
Text="{Binding Path=ItemCount}"/>
<TextBlock Text="( " />
<TextBlock Text="Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</tk:DataGrid.GroupStyle>
</tk:DataGrid>
Window.cs Code Behind:
public partial class Window8 : Window
{
private Dictionary<string, bool> _dict;
public Window8()
{
InitializeComponent();
_dict = new Dictionary<string, bool>();
var list1 = new List<MyKeyValuePair<string, int>>();
var random = new Random();
for (int i = 0; i < 50; i++)
{
list1.Add(new MyKeyValuePair<string, int>(
i.ToString(), random.Next(300) % 3));
}
var colView = new ListCollectionView(list1);
colView.GroupDescriptions.Add(
new PropertyGroupDescription("Value"));
this.DataContext = colView;
}
private void MyDataGrid_CellEditEnding
(object sender, DataGridCellEditEndingEventArgs e)
{
var dg = sender as DataGrid;
var cellInfo = dg.CurrentCell;
var mySource = dg.ItemsSource as ListCollectionView;
var oldDlg
= new CurrentChangingEventHandler((obj, args) => { return; });
var dlg = new CurrentChangingEventHandler(
(obj, args) =>
{
if (cellInfo.Item == mySource.CurrentItem)
{
var grpDescs = mySource.GroupDescriptions;
var oldGrpDescs
= grpDescs.Cast<PropertyGroupDescription>().ToList();
mySource.Dispatcher.BeginInvoke(
new Action(
() =>
{
grpDescs.Clear();
foreach (var grdpDesc in oldGrpDescs)
{
grpDescs.Add(grdpDesc);
}
mySource.CurrentChanging -= oldDlg;
}));
}
});
oldDlg = dlg;
mySource.CurrentChanging -= oldDlg;
mySource.CurrentChanging += oldDlg;
}
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
var exp = sender as Expander;
var dc = exp.DataContext as CollectionViewGroup;
_dict[dc.Name.ToString()] = exp.IsExpanded;
}
private void Expander_Initialized(object sender, EventArgs e)
{
var exp = sender as Expander;
var dc = exp.DataContext as CollectionViewGroup;
if (_dict != null
&& _dict.ContainsKey(dc.Name.ToString())
&& _dict[dc.Name.ToString()])
{
exp.IsExpanded = true;
}
}
}
But there are two trade offs.
This will make each cell edit attempt slow for large number of items in the datagrid. Because regrouping takes place after each of the cell edit attempt.
May not work for multiple group descriptions as the Name key in the dictionary may / may not stay unique. E.g. Assume for employee list if you group on FirstName and also gropup by LastName there will be nested expanders. Now some first name grouping may match with some last name grouping such as George. So the dictionary will fall into a trick and will not work correctly.
Hope this helps.

Coach a newbie through basic WPF? (I Do Not Grok It.)

Question is as it sounds. No matter how hard and how often I try to understand WPF, I feel like I'm hitting my head against a wall. I like Winforms, where everything makes sense.
As an example, I'm trying to write a simple app that will allow me to lay out a bunch of 2-D paths (represented by polylines) and drag their vertices around, and have the vertex information synchronised with a presenter (that is, I suppose, a ViewModel)
So the problem is thus:
Make the window recognise an instance of IExtendedObjectPresenter as a data source;
From a collection of IExtendedObject, draw one polyline for each IExtendedObject;
For each vertex in the extended object, represented by the collection IExtendedObject.Points, place a polyline vertex at the specified co-ordinates.
The IDE is giving me no help at all here. None of the many properties available in XAML sound meaningful to me. Because so much seems to be done implicitly, there is no obvious place to just tell the window what to do.
Before I get slated and told to RTFM, I would like to re-emphasise that I have researched the basic concepts of WPF a great many times. I know no more about it than I did when it was first released. It seems totally inpenetrable. Examples given for one type of behaviour are not in any way applicable to an even slightly different kind of behaviour, so you're back to square one. I'm hoping that repetition and targeted exampes might switch a light on in my head at some point.
I sympathize with you. Really understanding WPF takes a long time and it can be very frustrating to accomplish the simplest of things. But diving into a problem that is not easy for experts is only asking for trouble. You need to tackle simpler tasks and read a lot of code until things start to make sense. Donald Knuth says you don't really know the material until you do the exercises.
I solved your problem and I admit there are a lot of advance concepts in doing this cleanly and tacking on MVVM makes it that much harder. For what it's worth, here is a zero code-behind solution to your problem that is in the spirit of MVVM.
Here is the XAML:
<Grid>
<Grid.Resources>
<local:PolylineCollection x:Key="sampleData">
<local:Polyline>
<local:Coordinate X="50" Y="50"/>
<local:Coordinate X="100" Y="100"/>
<local:Coordinate X="50" Y="150"/>
</local:Polyline>
</local:PolylineCollection>
</Grid.Resources>
<Grid DataContext="{StaticResource sampleData}">
<ItemsControl ItemsSource="{Binding Segments}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="Black" StrokeThickness="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding ControlPoints}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Margin="-10,-10,0,0" Width="20" Height="20" Stroke="DarkBlue" Fill="Transparent">
<i:Interaction.Behaviors>
<local:ControlPointBehavior/>
</i:Interaction.Behaviors>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Grid>
and here are the supporting classes:
public class Coordinate : INotifyPropertyChanged
{
private double x;
private double y;
public double X
{
get { return x; }
set { x = value; OnPropertyChanged("X", "Point"); }
}
public double Y
{
get { return y; }
set { y = value; OnPropertyChanged("Y", "Point"); }
}
public Point Point
{
get { return new Point(x, y); }
set { x = value.X; y = value.Y; OnPropertyChanged("X", "Y", "Point"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(params string[] propertyNames)
{
foreach (var propertyName in propertyNames)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Polyline : List<Coordinate>
{
}
public class Segment
{
public Coordinate Start { get; set; }
public Coordinate End { get; set; }
}
public class PolylineCollection : List<Polyline>
{
public IEnumerable<Segment> Segments
{
get
{
foreach (var polyline in this)
{
var last = polyline.FirstOrDefault();
foreach (var coordinate in polyline.Skip(1))
{
yield return new Segment { Start = last, End = coordinate };
last = coordinate;
}
}
}
}
public IEnumerable<Coordinate> ControlPoints
{
get
{
foreach (var polyline in this)
{
foreach (var coordinate in polyline)
yield return coordinate;
}
}
}
}
public class ControlPointBehavior : Behavior<FrameworkElement>
{
private bool mouseDown;
private Vector delta;
protected override void OnAttached()
{
var canvas = AssociatedObject.Parent as Canvas;
AssociatedObject.MouseLeftButtonDown += (s, e) =>
{
mouseDown = true;
var mousePosition = e.GetPosition(canvas);
var elementPosition = (AssociatedObject.DataContext as Coordinate).Point;
delta = elementPosition - mousePosition;
AssociatedObject.CaptureMouse();
};
AssociatedObject.MouseMove += (s, e) =>
{
if (!mouseDown) return;
var mousePosition = e.GetPosition(canvas);
var elementPosition = mousePosition + delta;
(AssociatedObject.DataContext as Coordinate).Point = elementPosition;
};
AssociatedObject.MouseLeftButtonUp += (s, e) =>
{
mouseDown = false;
AssociatedObject.ReleaseMouseCapture();
};
}
}
This solution uses behaviors, which are ideal for implementing interactivity with MVVM.
If you are not familiar with behaviors, Install the Expression Blend 4 SDK and add this namespaces:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and add System.Windows.Interactivity to your project.
I'll show how to build a WPF application with MVVM pattern for 2D-Poliline with draggable vertexes.
PointViewModel.cs
public class PointViewModel: ViewModelBase
{
public PointViewModel(double x, double y)
{
this.Point = new Point(x, y);
}
private Point point;
public Point Point
{
get { return point; }
set
{
point = value;
OnPropertyChanged("Point");
}
}
}
The class ViewModelBase contains only an implementation of interface INotifyPropertyChanged. This is necessary for reflecting changes of the clr-property on the visual representation.
LineViewModel.cs
public class LineViewModel
{
public LineViewModel(PointViewModel start, PointViewModel end)
{
this.StartPoint = start;
this.EndPoint = end;
}
public PointViewModel StartPoint { get; set; }
public PointViewModel EndPoint { get; set; }
}
It has references to Points, so the changes will be received automatically.
MainViewModel.cs
public class MainViewModel
{
public MainViewModel()
{
this.Points = new List<PointViewModel>
{
new PointViewModel(30, 30),
new PointViewModel(60, 100),
new PointViewModel(50, 120)
};
this.Lines = this.Points.Zip(this.Points.Skip(1).Concat(this.Points.Take(1)),
(p1, p2) => new LineViewModel(p1, p2)).ToList();
}
public List<PointViewModel> Points { get; set; }
public List<LineViewModel> Lines { get; set; }
}
It contains a sample data of points and lines
MainVindow.xaml
<Window.Resources>
<ItemsPanelTemplate x:Key="CanvasPanelTemplate">
<Canvas/>
</ItemsPanelTemplate>
<Style x:Key="PointListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Point.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Point.Y}"/>
</Style>
<DataTemplate x:Key="LineTemplate">
<Line X1="{Binding StartPoint.Point.X}" X2="{Binding EndPoint.Point.X}" Y1="{Binding StartPoint.Point.Y}" Y2="{Binding EndPoint.Point.Y}" Stroke="Blue"/>
</DataTemplate>
<DataTemplate x:Key="PointTemplate">
<view:PointView />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Lines}" ItemsPanel="{StaticResource CanvasPanelTemplate}" ItemTemplate="{StaticResource LineTemplate}"/>
<ItemsControl ItemsSource="{Binding Points}" ItemContainerStyle="{StaticResource PointListBoxItem}" ItemsPanel="{StaticResource CanvasPanelTemplate}"
ItemTemplate="{StaticResource PointTemplate}"/>
</Grid>
Here is a lot of tricks. First of all, these ItemsControls are based not on vertical StackPanel, but on Canvas. The ItemsControl of points applies a special container template with a goal to place items on necessary coordinates. But the ItemsControl of lines don't require such templates, and it is strange at some point. Two last DataTemplates are obvious.
PointView.xaml
<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/>
Left and Top margins are equal to a half of the Width and the Height. We have a transparent Fill because this property doesn't have a default value and the events of mouse don't work.
That's almost all. Only drag-n-drop functionality remains.
PointView.xaml.cs
public partial class PointView : UserControl
{
public PointView()
{
InitializeComponent();
this.MouseLeftButtonDown += DragSource_MouseLeftButtonDown;
this.MouseMove += DragSource_MouseMove;
}
private bool isDraggingStarted;
private void DragSource_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.isDraggingStarted = true;
}
private void DragSource_MouseMove(object sender, MouseEventArgs e)
{
if (isDraggingStarted == true)
{
var vm = this.DataContext as PointViewModel;
var oldPoint = vm.Point;
DataObject data = new DataObject("Point", this.DataContext);
DragDropEffects effects = DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
if (effects == DragDropEffects.None) //Drag cancelled
vm.Point = oldPoint;
this.isDraggingStarted = false;
}
}
MainVindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
this.AllowDrop = true;
this.DragOver += DropTarget_DragOver;
}
private void DropTarget_DragOver(object sender, DragEventArgs e)
{
var vm = e.Data.GetData("Point") as PointViewModel;
if (vm != null)
vm.Point = e.GetPosition(this);
}
}
So your sample is done using 2 xaml files and 3 viewmodels.

How to use WPF to visualize a simple 2D world (map and elements)

I'm fairly new to WPF and looking for a simple solution to the problem described below. I've tried to make this as short as possible.
I'm trying to visualize a "world" that is modeled by:
A map image, with a known origin in meters (e.g. top-left corner is 14,27) and resolution in cm/pixel. The map keeps growing every few seconds. Maps are small so no paging/tiling is required.
Real-world elements and points of interest. Each element has a 2D position in meters within the map area. Also, each element might move.
Regarding the model side, I have a WorldState class that keeps the map and elements:
interface IWorldState
{
IEnumerable<IWorldElement> Elements { get; }
IMapData CurrentMap { get; }
}
interface IWorldElement
{
WorldLocation { get; }
event EventHandler LocationChanged;
}
interface IMapData
{
string FilePath { get; }
WorldLocation TopLeft { get; }
Size MapSize { get; }
}
Now regarding the visualization, I've chosen the Canvas class to draw the map and elements. Each type of element (inherits from IWorldElement) should be drawn differently. There might be more than one map canvas, with a subset of the elements.
<Canvas x:Name="mapCanvas">
<Image x:Name="mapImage" />
</Canvas>
In code, I need to set the map image file when it changes:
void MapChanged(IWorldState worldState)
{
mapImage.Source = worldState.CurrentMap.FilePath;
}
To draw the elements, I have a method to convert WorldLocation to (Canvas.Left, Canvas.Top) :
Point WorldToScreen(WorldLocation worldLocation, IWorldState worldState)
{
var topLeft = worldState.CurrentMap.TopLeft;
var size = worldState.CurrentMap.Size;
var left = ((worldLocation.X - topLeft.X) / size.X) * mapImage.ActualWidth;
var top = ((worldLocation.Y - topLeft.Y) / size.Y) * mapImage.ActualHeight;
return new Point(left, top);
}
Now for the question, how should I glue the world model and the canvas together? This can be summarized by:
Where to put the MapChanged and WorldToScreen functions.
When an element moves the world location needs to be converted to screen coordinates.
Each type of element should be drawn differently, for example ellipse with text or filled rectangle.
What is the recommended way to implement the "glue" layer when using WPF?
DataBinding is the way to go.
Read here, http://msdn.microsoft.com/en-us/magazine/dd419663.aspx.
Once you've set up a viewmodel and set the datacontext of the view.
I suggest putting your elements in an observablecollection and bind it to an itemscontrol in the canvas.
For the elements to be positioned, you must create a custom ItemTemplate for the ItemsControl which uses a canvas, rather than the default stackpanel as container.
Then you can go ahead and create datatemplate for the various types of elements you have to get a specific look and feel pr element type.
This is a rough outline of a solution, hope this helps.
Example:
<Window x:Class="WorldCanvas.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WorldCanvas"
Title="WorldCanvas" Height="500" Width="500"
>
<Window.Resources>
<DataTemplate DataType="{x:Type local:HouseVM}" >
<Canvas>
<Rectangle Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="13" Height="23" Fill="Brown" />
</Canvas>
</DataTemplate>
<DataTemplate DataType="{x:Type local:BallVM}">
<Canvas>
<Ellipse Canvas.Left="{Binding X}" Canvas.Top="{Binding Y}" Width="13" Height="13" Fill="Blue" />
</Canvas>
</DataTemplate>
</Window.Resources>
<Grid>
<Canvas x:Name="TheWorld" Background="DarkGreen">
<Button Content="MoveFirst" Click="Button_Click" />
<ItemsControl ItemsSource="{Binding Entities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
</Grid>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
var worldViewModel = new WorldViewModel();
DataContext = worldViewModel;
}
void Button_Click(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as WorldViewModel;
if(viewModel != null)
{
var entity = viewModel.Entities.First();
entity.X +=10;
}
}
}
Viewmodels
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class WorldViewModel : ViewModelBase
{
ObservableCollection<EntityVM> entities;
public ObservableCollection<EntityVM> Entities {
get { return entities; }
set
{
entities = value;
NotifyPropertyChanged("Entities");
}
}
public WorldViewModel()
{
Entities = new ObservableCollection<EntityVM>();
int y=0;
for(int i=0; i<30; i++)
{
if(i %2 == 0)
{
Entities.Add(new BallVM(i*10, y+=20));
}
else
{
Entities.Add(new HouseVM(i*20, y+=20));
}
}
}
}
public class EntityVM : ViewModelBase
{
public EntityVM(double x, double y)
{
X = x;
Y = y;
}
private double _x;
public double X
{
get
{
return _x;
}
set
{
_x = value;
NotifyPropertyChanged("X");
}
}
private double _y;
public double Y
{
get
{
return _y;
}
set
{
_y = value;
NotifyPropertyChanged("Y");
}
}
}
public class BallVM : EntityVM
{
public BallVM(double x, double y) : base(x, y)
{
}
}
public class HouseVM : EntityVM
{
public HouseVM(double x, double y) : base(x, y)
{
}
}

How to enable horizontal scrolling with mouse?

I cannot determine how to scroll horizontally using the mouse wheel. Vertical scrolling works well automatically, but I need to scroll my content horizontally. My code looks like this:
<ListBox x:Name="receiptList"
Margin="5,0"
Grid.Row="1"
ItemTemplate="{StaticResource receiptListItemDataTemplate}"
ItemsSource="{Binding OpenReceipts}"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
ScrollViewer.HorizontalScrollBarVisibility="Visible"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
My item template looks like this:
<DataTemplate x:Key="receiptListItemDataTemplate">
<RadioButton GroupName="Numbers"
Command="{Binding Path=DataContext.SelectReceiptCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type POS:PointOfSaleControl}}}"
CommandParameter="{Binding }"
Margin="2,0"
IsChecked="{Binding IsSelected}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}" >
<Grid x:Name="receiptGrid" >
<Grid>
<Border BorderThickness="2"
BorderBrush="Green"
Height="20"
Width="20">
<Grid x:Name="radioButtonGrid"
Background="DarkOrange">
<TextBlock x:Name="receiptLabel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Path=NumberInQueue, Mode=OneWay}"
FontWeight="Bold"
FontSize="12"
Foreground="White">
</TextBlock>
</Grid>
</Border>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Margin"
TargetName="receiptGrid"
Value="2,2,-1,-1"/>
<Setter Property="Background"
TargetName="radioButtonGrid"
Value="Maroon"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
Is there another method or control that I need to add to get that functionality?
Here's a complete behaviour. Add the below class to your code, then in your XAML set the attached property to true on any UIElement that contains a ScrollViewer as a visual child.
<MyVisual ScrollViewerHelper.ShiftWheelScrollsHorizontally="True" />
The class:
public static class ScrollViewerHelper
{
public static readonly DependencyProperty ShiftWheelScrollsHorizontallyProperty
= DependencyProperty.RegisterAttached("ShiftWheelScrollsHorizontally",
typeof(bool),
typeof(ScrollViewerHelper),
new PropertyMetadata(false, UseHorizontalScrollingChangedCallback));
private static void UseHorizontalScrollingChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if (element == null)
throw new Exception("Attached property must be used with UIElement.");
if ((bool)e.NewValue)
element.PreviewMouseWheel += OnPreviewMouseWheel;
else
element.PreviewMouseWheel -= OnPreviewMouseWheel;
}
private static void OnPreviewMouseWheel(object sender, MouseWheelEventArgs args)
{
var scrollViewer = ((UIElement)sender).FindDescendant<ScrollViewer>();
if (scrollViewer == null)
return;
if (Keyboard.Modifiers != ModifierKeys.Shift)
return;
if (args.Delta < 0)
scrollViewer.LineRight();
else
scrollViewer.LineLeft();
args.Handled = true;
}
public static void SetShiftWheelScrollsHorizontally(UIElement element, bool value) => element.SetValue(ShiftWheelScrollsHorizontallyProperty, value);
public static bool GetShiftWheelScrollsHorizontally(UIElement element) => (bool)element.GetValue(ShiftWheelScrollsHorizontallyProperty);
[CanBeNull]
private static T FindDescendant<T>([CanBeNull] this DependencyObject d) where T : DependencyObject
{
if (d == null)
return null;
var childCount = VisualTreeHelper.GetChildrenCount(d);
for (var i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(d, i);
var result = child as T ?? FindDescendant<T>(child);
if (result != null)
return result;
}
return null;
}
}
This answer fixes a few bugs in Johannes' answer, such as not filtering by Shift key, scrolling both horizontally and vertically at the same time (motion was diagonal) and the inability to disable the behaviour by setting the property to false.
I wrote an Attached Property for this purpose to reuse it on every ItemsControl containing an ScrollViewer. FindChildByType is an Telerik extension but can also be found here.
public static readonly DependencyProperty UseHorizontalScrollingProperty = DependencyProperty.RegisterAttached(
"UseHorizontalScrolling", typeof(bool), typeof(ScrollViewerHelper), new PropertyMetadata(default(bool), UseHorizontalScrollingChangedCallback));
private static void UseHorizontalScrollingChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
ItemsControl itemsControl = dependencyObject as ItemsControl;
if (itemsControl == null) throw new ArgumentException("Element is not an ItemsControl");
itemsControl.PreviewMouseWheel += delegate(object sender, MouseWheelEventArgs args)
{
ScrollViewer scrollViewer = itemsControl.FindChildByType<ScrollViewer>();
if (scrollViewer == null) return;
if (args.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
};
}
public static void SetUseHorizontalScrolling(ItemsControl element, bool value)
{
element.SetValue(UseHorizontalScrollingProperty, value);
}
public static bool GetUseHorizontalScrolling(ItemsControl element)
{
return (bool)element.GetValue(UseHorizontalScrollingProperty);
}
The easiest way is to add PreviewMouseWheel listener to the ScrollViewer, check for shift (or whatever you want to do to indicate horizontal scroll), and then call LineLeft or LineRight (or PageLeft / PageRight) depending on the value of the Delta value of the MouseWheelEventArgs
I was kinda looking for the most simple way to make any ScrollViewer scroll left-right instead of up-down. So here is the simplest combination of the other answers.
<ScrollViewer HorizontalScrollBarVisibility="Visible"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
and:
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scrollViewer = (ScrollViewer)sender;
if (e.Delta < 0)
{
scrollViewer.LineRight();
}
else
{
scrollViewer.LineLeft();
}
e.Handled = true;
}
This is basically what John Gardner suggested just with the actual code. I'm also quoting my answer from similar question here.
(sender as ScrollViewer).ScrollToHorizontalOffset( (sender as ScrollViewer).ContentHorizontalOffset + e.Delta);
Try this:
<ListBox x:Name="receiptList"
Margin="5,0"
Grid.Row="1"
ItemTemplate="{StaticResource receiptListItemDataTemplate}"
ItemsSource="{Binding OpenReceipts}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListBox>
UPDATE Ooops, missed the part about the mouse wheel! Sorry
To make the mouse wheel work, you will have to subscribe to the mouse wheel event and manuaaly move the scroll bar... This can nicelly be encapsulated by a behavior but I think thats the only way to make it work!

FlowDocumentPageViewer SearchText and Highlighting

I am creating a search and highlighting the text using the FlowDocumentPageViewer, something similar to the link given.
http://kentb.blogspot.com/2009/06/search-and-highlight-text-in-arbitrary.html
When I search for Tokens of string, (using a list of string) everything works fine and I get my rectangle applied appropriately. But I have two problems here,
When I change the pages of the FlowDocumentPageViewer, my Rectangular highlighted area remains the same and it is not sinking with the Text.
When I zoom in or zoom out of the FlowDocumentPageViewer, the text gets zoomed but the Highlight rectangle remains in the same position,
Can you please help me in resolving this problem such that the rectangle gets applied to the Text itself. I am posing my application here. Please let me know still if you need further information.
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBox x:Name="_searchTextBox" Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="20" Margin="2"/>
<Button x:Name="_searchButton" Height="{Binding Height, ElementName=_searchTextBox}" Width="50" Content="GO">
</Button>
<Button x:Name="_listItems" Height="{Binding Height, ElementName=_searchTextBox}" Width="50" Content="List"/>
</StackPanel>
<Grid Grid.Row="1">
<FlowDocumentPageViewer>
<FlowDocument Foreground="Black" FontFamily="Arial">
<Paragraph FontSize="11">
The following details have been details from Amazon to match your initial query.Some of the returned values may have been empty, so have been ommitted from theresults shown here.Also where there have been more than one value returned viathe Amazon Details, these to have beenomitted for the sake of keeping things simplefor this small demo application. Simple is good,when trying to show how something works
</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
</Grid>
<ItemsControl ItemsSource="{Binding SearchRectangles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="#99FFFF00" Width="{Binding Width}" Height="{Binding Height}" Tag="{Binding Text}" MouseDown="Rectangle_MouseDown">
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BitmapEffect">
<Setter.Value>
<OuterGlowBitmapEffect GlowColor="BurlyWood" GlowSize="7"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Top}"/>
<Setter Property="Canvas.Left" Value="{Binding Left}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</Grid>
public partial class Window1 : Window
{
public static readonly DependencyProperty SearchTextProperty = DependencyProperty.Register("SearchText",
typeof(string),
typeof(Window1));
public static readonly DependencyProperty SearchRectanglesProperty = DependencyProperty.Register("SearchRectangles",
typeof(ICollection<SearchRectangle>),
typeof(Window1));
public IList<string> SearchTokens { get; set; }
public Window1()
{
InitializeComponent();
SearchRectangles = new ObservableCollection<SearchRectangle>();
_searchButton.Click += delegate
{
DoSearch();
};
_listItems.Click += delegate
{
SearchTokens = new List<string>();
SearchTokens.Add("been");
SearchTokens.Add("Amazon");
SearchTokens.Add("following");
DoSearch(SearchTokens);
};
_searchTextBox.KeyDown += delegate(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DoSearch();
}
};
}
public void DoSearch(IList<string> searchTokens)
{
SearchRectangles.Clear();
if (searchTokens == null)
return;
foreach (string token in searchTokens)
{
SearchText = token;
DoSearch();
}
}
public string SearchText
{
get { return GetValue(SearchTextProperty) as string; }
set { SetValue(SearchTextProperty, value); }
}
public ICollection<SearchRectangle> SearchRectangles
{
get { return GetValue(SearchRectanglesProperty) as ICollection<SearchRectangle>; }
set { SetValue(SearchRectanglesProperty, value); }
}
private void DoSearch()
{
DoSearch(false);
}
private void DoSearch(bool clearExisting)
{
if( clearExisting == true )
SearchRectangles.Clear();
if (SearchText.Length == 0)
{
return;
}
DoSearch(this);
}
private void DoSearch(DependencyObject searchIn)
{
if (searchIn == null)
{
return;
}
var contentHost = searchIn as IContentHost;
if (contentHost != null)
{
DoSearch(contentHost as UIElement, contentHost);
}
else
{
var documentViewerBase = searchIn as DocumentViewerBase;
if (documentViewerBase != null)
{
//extract the content hosts from the document viewer
foreach (var pageView in documentViewerBase.PageViews)
{
contentHost = pageView.DocumentPage as IContentHost;
if (contentHost != null)
{
DoSearch(documentViewerBase, contentHost);
}
}
}
}
//recurse through children
var childCount = VisualTreeHelper.GetChildrenCount(searchIn);
for (var i = 0; i < childCount; ++i)
{
DoSearch(VisualTreeHelper.GetChild(searchIn, i));
}
}
private void DoSearch(UIElement uiHost, IContentHost contentHost)
{
if (uiHost == null)
{
return;
}
var textBlock = contentHost as TextBlock;
if (textBlock != null)
{
//this has the side affect of converting any plain string content in the TextBlock into a hosted Run element
//that's bad in that it is unexpected, but good in that it allows us to access the hosted elements in a
//consistent fashion below, rather than special-casing TextBlocks with text only content
var contentStart = textBlock.ContentStart;
}
var hostedElements = contentHost.HostedElements;
while (hostedElements.MoveNext())
{
var run = hostedElements.Current as Run;
if (run != null && !string.IsNullOrEmpty(run.Text))
{
ApplyHighlighting(run.Text, delegate(int start, int length)
{
var textPointer = run.ContentStart;
textPointer = textPointer.GetPositionAtOffset(start, LogicalDirection.Forward);
var leftRectangle = textPointer.GetCharacterRect(LogicalDirection.Forward);
textPointer = textPointer.GetPositionAtOffset(length, LogicalDirection.Forward);
var rightRectangle = textPointer.GetCharacterRect(LogicalDirection.Backward);
var rect = new Rect(leftRectangle.TopLeft, rightRectangle.BottomRight);
var translatedPoint = uiHost.TranslatePoint(new Point(0, 0), null);
rect.Offset(translatedPoint.X, translatedPoint.Y);
return rect;
});
}
}
}
private void ApplyHighlighting(string text, Func<int, int, Rect> getRectHandler)
{
var currentIndex = 0;
while (true)
{
var index = text.IndexOf(SearchText, currentIndex, StringComparison.CurrentCultureIgnoreCase);
if (index == -1)
{
return;
}
var rect = getRectHandler(index, SearchText.Length);
if (rect != Rect.Empty)
{
SearchRectangles.Add(new SearchRectangle(rect.Top, rect.Left, rect.Width, rect.Height,SearchText));
}
currentIndex = index + SearchText.Length;
}
}
private void Rectangle_MouseDown(object sender, MouseEventArgs e)
{
Rectangle r = sender as Rectangle;
MessageBox.Show(r.Tag.ToString());
}
private void FlowDocumentPageViewer_PageViewsChanged(object sender, EventArgs e)
{
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
DoSearch(SearchTokens);
}
}
public class SearchRectangle
{
private readonly double _top;
private readonly double _left;
private readonly double _width;
private readonly double _height;
private readonly string _text;
public SearchRectangle(double top, double left, double width, double height,string text)
{
_top = top;
_left = left;
_width = width;
_height = height;
_text = text;
}
public string Text
{
get { return _text; }
}
public double Top
{
get { return _top; }
}
public double Left
{
get { return _left; }
}
public double Width
{
get { return _width; }
}
public double Height
{
get { return _height; }
}
}
Best,
Bala.
What would appear to be an easy solution is to actually edit the document to do the hilighting as you search, remembering to edit it back when you clear the search results or need the unedited document for other purposes (such as to save it).
You can search a document for text using the code I posted in this answer. In your case, instead of setting the selection to each range found, you'll want to hilight the range like this:
public void HilightMatches(TextRange searchRange, string searchText)
{
var start = searchRange.Start;
while(start != searchRange.End)
{
var foundRange = FindTextInRange(
new TextRange(start, searchRange.End),
searchText);
if(foundRange==null) return;
// Here is the meat...
foundRange.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
start = foundRange.End;
}
}
To complete the picture, you'll need a way to clear the hilighting when you're done. Several options:
Save the ranges that were hilighted so you can just use ApplyPropertyValue to apply Brushes.Transparent background (simple, but this will erase any background that was previously set)
Split each range that was found into runs and save the lists of runs each with its original background color, so it will be easy to restore
Save the entire document before you muck with it and just restore it to remove the hilighting
If the document is in a RichTextBox and the user won't be allowed to edit the document, you can simply use the undo mechanism. Make sure your TextBoxBase.UndoLimit is increased as you do the hilighting, then to remove the hilighting just call TextBoxBase.Undo() once per hilight.
Try to this solution
http://rredcat.blogspot.com/2009/11/zoom-and-page-chahged-events-for.html

Resources