FlowDocumentPageViewer SearchText and Highlighting - wpf

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

Related

drawing new lines in an already populated canvas

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.

Stopping user from selecting/unselecting rows in WPF DataGrid

I have a DataGrid on a WPF page and want to prevent the user from selecting cells. As this feature is needed just for testing, I don't want to change everything in code.
After my DataGrid is filled, I make sure all of its rows are selected. Now I want to make sure that user cannot select/unselect rows.
I tried setting IsEnabled = false and IsHitTestVisible = "False" but both of these solutions disable scrollbars.
Is there any way to do this?
Why not just set IsHitTestVisible="False" for your DataGridRow or DataGridCell objects only?
That's easy to do using an implicit style in the <DataGrid.Resources>, and should only disable hit-testing on the rows or cells, which should leave the other areas of the DataGrid functional, such as the Headers or ScrollBars
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
</DataGrid.Resources>
You have two choices:
You disable selection in style (in this case you turn off only color in style, but physically SelectedItem or SelectedItems will change). You can easily find out how you can turn off selection style.
You can disable changing selection without changing SelectedItem or SelectedItems (in this case your selection style will not change too).
In WPF i don't like to override standard controls. So, we need a Behavior:
public class DisableSelectionDataGridBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectOnPreviewMouseLeftButtonDown;
}
private void AssociatedObjectOnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var dependencyObject = AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)) as DependencyObject;
if (dependencyObject == null) return;
var elements = dependencyObject.GetParents().OfType<FrameworkElement>().Where(DataGridCellExtended.GetIsDisableSelection).ToList();
if (!elements.Any()) return;
e.Handled = true;
var args = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
args.RoutedEvent = UIElement.MouseLeftButtonDownEvent;
args.Source = e.Source;
elements.ForEach(item =>
{
item.RaiseEvent(args);
var children = item.GetChildren<FrameworkElement>();
children.ForEach(child => child.RaiseEvent(args));
});
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectOnPreviewMouseLeftButtonDown;
}
}
Second, you need an Extended class:
public class DataGridCellExtended
{
public static readonly DependencyProperty IsDisableSelectionProperty = DependencyProperty.RegisterAttached("IsDisableSelection", typeof(Boolean), typeof(DataGridCellExtended));
public static Boolean GetIsDisableSelection(DependencyObject o)
{
return (Boolean)o.GetValue(IsDisableSelectionProperty);
}
public static void SetIsDisableSelection(DependencyObject o, Boolean value)
{
o.SetValue(IsDisableSelectionProperty, value);
}
}
And finally in XAML you need something like this:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type items:YourViewModel}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button Margin="0"
extends:DataGridCellExtended.IsDisableSelection="True">
<Path Data="M5,0L3,2 1,0 0,1 2,3 0,5 1,6 3,4 5,6 6,5 4,3 6,1z"
Fill="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridCell}}"
Width="12"
Height="12"
Stretch="Uniform"/>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
You can write your logic for extended class.
public static IEnumerable<DependencyObject> GetParents(this DependencyObject element)
{
if (element != null)
{
while (true)
{
var parent = element.GetParent();
var dependencyObject = parent;
element = parent;
if (dependencyObject == null)
{
break;
}
yield return element;
}
yield break;
}
else
{
throw new ArgumentNullException("element");
}
}
private static IEnumerable<DependencyObject> GetChildrenRecursive(this DependencyObject element)
{
if (element != null)
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var dependencyObject = VisualTreeHelper.GetChild(element, i);
yield return dependencyObject;
foreach (var childrenRecursive in dependencyObject.GetChildrenRecursive())
{
yield return childrenRecursive;
}
}
}
else
{
throw new ArgumentNullException("element");
}
}

How to run a progress bar in different thread in WPF?

I am populating the list view items dynamically. At the same time I wanna display a progress bar. When data populated the progress bar should be disabled. How to achieve this.
I am new in WPF.
You can use the BackgroundWorker class, which simplifies the handling of background threads when you are working with WPF.
There are plenty of examples on the web for this. Here two from codeproject, but it's easy to find more examples:
http://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo.aspx
http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx
I prefer to control view state via presentation model. When view needs to populate items in address to presentation model that starts Worker thread and updates its progress values on UI synchronization context.
public class SampleModel : ObservableObject
{
private ObservableCollection<string> _items = new ObservableCollection<string>();
public IEnumerable<string> Items
{
get
{
return this._items;
}
}
private int _progress;
public int Progress
{
get
{
return this._progress;
}
set
{
if (this._progress != value)
{
this._progress = value;
this.OnPropertyChanged("Progress");
}
}
}
public void Fill()
{
this.Progress = 0;
var sc = SynchronizationContext.Current;
new Thread(new ThreadStart(() =>
{
for (int i = 0; i < 100; i++)
{
sc.Post(p =>
{
this._items.Add(i.ToString());
this.Progress ++;
}, null);
Thread.Sleep(100);
}
sc.Post(p =>
{
this.Progress = 0;
}, null);
}))
.Start();
}
}
XAML:
<DockPanel>
<ProgressBar Minimum="0"
Maximum="100"
Height="50"
Value="{Binding Progress}"
DockPanel.Dock="Top">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="IsEnabled"
Value="True"/>
</Style>
</ProgressBar.Style>
</ProgressBar>
<Button Name="Start"
DockPanel.Dock="Top">Start</Button>
<ListView Name="List"
ItemsSource="{Binding Items}"/>
</DockPanel>
And code behind:
public MainWindow()
{
InitializeComponent();
this.Model = new SampleModel();
this.Start.Click += new RoutedEventHandler(Start_Click);
}
void Start_Click(object sender, RoutedEventArgs e)
{
this.Model.Fill();
}
protected SampleModel Model
{
get
{
return (SampleModel)this.DataContext;
}
set
{
this.DataContext = value;
}
}

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.

WPF List View Sort on Load

Here is the problem:
I want to sort a ListView when it is first loaded.
I have implemented the functionality where in the List View can be sorted if the Header columns
in the ListView are clicked.
I unable to find a suitable event which I can use to call my sort function.
I tried using OnInitialized of the UserControl and Loaded events but it seems the List View is
not populated when I call these functions.
I tried GotFocus of ListView. It works but then I have to click on the window to get the sorting done.
I want the sorting to be done as soon as the ListView is loaded.
I am using XML data binding with the List View.
The ListView is part of a UserControl. The User Control is hosted in a MMC app.
Please let me know if you need any other information.
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
bool newSortColumn = false;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
newSortColumn = true;
direction = ListSortDirection.Ascending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
// cast the ListView's View to a GridView
GridView gridView = this.View as GridView;
if (gridView != null)
{
// determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.1
SortableGridViewColumn sortableGridViewColumn = null;
foreach (GridViewColumn gridViewColumn in gridView.Columns)
{
sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;
if (sortableGridViewColumn != null)
{
if (sortableGridViewColumn.IsDefaultSortColumn)
{
break;
}
sortableGridViewColumn = null;
}
}
// if the default sort column is defined, sort the data
if (sortableGridViewColumn != null)
{
lastSortedOnColumn = sortableGridViewColumn;
Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);
}
}
}
}
The XAML is as shown below:
**<local:SortableListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<local:SortableGridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#id}"
IsDefaultSortColumn="True"
SortPropertyName="#id"/>
<local:SortableGridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
CellTemplateSelector="{DynamicResource myControlTemplateSelector}"
SortPropertyName="#value"/>
<local:SortableGridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#data_type}"
SortPropertyName="#data_type"/>
<local:SortableGridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
SortPropertyName="#description"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</local:SortableListView>**
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>
I finally was able to resolve it.
I had to use the Converter on ListView ItemSource. And then sort the List on Convert Function.
Here is the code below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Threading;
using System.Collections.ObjectModel;
namespace ...MiscellaneousCAESettings
{
/// <summary>
/// Interaction logic for ConfigDataView.xaml
/// </summary>
public partial class ConfigDataView : UserControl, IConfigDataViewControl
{
ConfigDataViewPresenter _presenter = null;
public static string _currDataType = "";
public static string _min = "" ;
public static string _max = "";
public string Min
{
get
{
return _min;
}
set
{
_min = value ;
}
}
public string Max
{
get
{
return _max;
}
set
{
_max = value;
}
}
public string CurrDataType
{
get
{
return _currDataType;
}
set
{
_currDataType = value;
}
}
public ConfigDataView()
{
InitializeComponent();
//To give the classic windows look
Uri uri = new Uri("PresentationFramework.Classic;V3.0.0.0;31bf3856ad364e35;component\\themes/classic.xaml", UriKind.Relative);
this.Resources.MergedDictionaries.Add(Application.LoadComponent(uri) as ResourceDictionary);
}
private void txtBoxGotFocus(object sender, RoutedEventArgs e)
{
Min = "" ;
Max = "" ;
TextBox txtbox = e.Source as TextBox;
this.ListViewControl.SelectedItem = txtbox.DataContext;
//index
int index = this.ListViewControl.Items.IndexOf(this.ListViewControl.SelectedItem);
System.ComponentModel.ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ListViewControl.ItemsSource);
object stCurr = (dataView.CurrentPosition ) ;
//Check if the "data_type" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["data_type"] != null)
{
CurrDataType = ((XmlElement)dataView.CurrentItem).Attributes["data_type"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["min"] != null)
{
Min = ((XmlElement)dataView.CurrentItem).Attributes["min"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["max"] != null)
{
Max = ((XmlElement)dataView.CurrentItem).Attributes["max"].Value;
}
}
#region IConfigDataViewControl Members
public void LoadRootConfigData(string xmlFileName, string xmlFileContent, string xmlXPath)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
if (string.IsNullOrEmpty(xmlXPath))
{
xmldp.XPath = #"//node()[1]/node()[#value]";
}
else
{
xmldp.XPath = xmlXPath;
}
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void LoadCategoryConfigData(string xmlFile, string xmlFileContent, string CategoryNodeName)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
xmldp.XPath = #"//CONTEXT[#id='" + CategoryNodeName + #"']/CONFIGURATION";
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void AttachPresenter(ConfigDataViewPresenter cfgpresenter)
{
_presenter = cfgpresenter;
}
#endregion
private void btn_ApplyClick(object sender, RoutedEventArgs e)
{
XmlDataProvider odp = (XmlDataProvider)this.TryFindResource("dataProvider");
XmlDocument configFileDoc = new XmlDocument();
configFileDoc =odp.Document;
_presenter.Save(configFileDoc.InnerXml );
}
}
public class TextBoxMinMaxValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
try
{
//Check for min max string length if it is a "Text" data type
if (ConfigDataView._currDataType.ToLower() == "text")
{
int minLength = Convert.ToInt32(ConfigDataView._min);
int maxLength = Convert.ToInt32(ConfigDataView._max);
int strLength = value.ToString().Length;
bool isValidLength = true;
isValidLength = ((strLength >= minLength) && (strLength <= maxLength));
if (!isValidLength)
{
return new ValidationResult(false, string.Format("The input String Length is out of range. The String Length should be between {0} to {1}", minLength, maxLength));
}
else
{
return new ValidationResult(true, null);
}
}
//Check for min max string length if it is a "Numeric" data type
if (ConfigDataView._currDataType.ToLower() != "numeric")
{
return new ValidationResult(true, null);
}
int min = Convert.ToInt32(ConfigDataView._min);
int max = Convert.ToInt32(ConfigDataView._max);
int res ;
bool isNumber = int.TryParse(value.ToString(), out res);
bool isValidRange = true;
if (!isNumber)
{
return new ValidationResult(false, "The input string is in incorrect format. Should be a Number.");
}
isValidRange = ((res >= min) && (res <= max));
if (!isValidRange)
{
return new ValidationResult(false, string.Format("The input integer is out of range. The number should be between {0} to {1}", min, max));
}
}
catch
{
}
return new ValidationResult(true, null);
}
}
public class ControlTemplateSelector : DataTemplateSelector
{
public const String XML_TAG_DATATYPE = "data_type";
public const String DATATYPE_DROPDOWN = "Dropdown";
public const String DATATYPE_BOOLEAN = "Boolean";
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
FrameworkElement window = (container as FrameworkElement);
try
{
XmlNode node = (XmlNode)item;
String dataType = "";
if (node.Attributes[XML_TAG_DATATYPE] != null)
{
dataType = (string)node.Attributes.GetNamedItem(XML_TAG_DATATYPE).Value;
}
if (dataType == DATATYPE_DROPDOWN)
{
return window.FindResource("dropDownTemplate") as DataTemplate;
}
if (dataType == DATATYPE_BOOLEAN)
{
return window.FindResource("booldropDownTemplate") as DataTemplate;
}
}
catch (Exception ex)
{
MessageBox.Show("Select template Exception" + ex.Message );
}
return window.FindResource("textTemplate") as DataTemplate;
}
}
public class boolConverter : IValueConverter
{
public const String XML_TAG_VALUE = "value";
public const String XML_TAG_ID = "id";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
if (boolVal == true)
{
return 1;
}
else
{
return 0;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
return boolVal;
}
}
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
direction = ListSortDirection.Descending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
}
public class SortList : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//If the value is null tell binding engine to do nothing
if (value == null)
{
return Binding.DoNothing;
}
ListCollectionView view = (ListCollectionView)
CollectionViewSource.GetDefaultView(value);
SortDescription sort_isdisabled =
new SortDescription("#isenabled",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_isdisabled);
SortDescription sort_id =
new SortDescription("#id",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_id);
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
The XAML is as follows:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:.....MiscellaneousCAESettings"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="textTemplate">
<TextBox HorizontalAlignment= "Stretch"
IsEnabled="{Binding XPath=./#isenabled}"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
GotFocus="txtBoxGotFocus"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding XPath="./#value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:TextBoxMinMaxValidation>
<local:TextBoxMinMaxValidation.DataType>
<local:DataTypeCheck
Datatype="{Binding Source={StaticResource dataProvider}, XPath='/[#id=CustomerServiceQueueName]'}"/>
</local:TextBoxMinMaxValidation.DataType>
<local:TextBoxMinMaxValidation.ValidRange>
<local:Int32RangeChecker
Minimum="{Binding Source={StaticResource dataProvider}, XPath=./#min}"
Maximum="{Binding Source={StaticResource dataProvider}, XPath=./#max}"/>
</local:TextBoxMinMaxValidation.ValidRange>
</local:TextBoxMinMaxValidation>
</Binding.ValidationRules>
</Binding >
</TextBox.Text>
</TextBox>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value}"
ItemsSource="{Binding XPath=.//OPTION/#value}"
IsEnabled="{Binding XPath=./#isenabled}"
/>
</DataTemplate>
<DataTemplate x:Key="booldropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value, Converter={StaticResource boolconvert}}">
<ComboBoxItem>True</ComboBoxItem>
<ComboBoxItem>False</ComboBoxItem>
</ComboBox>
</DataTemplate>
<local:ControlTemplateSelector
x:Key="myControlTemplateSelector"/>
<Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template" Value="{DynamicResource ListBoxItemControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" Padding="{TemplateBinding Padding}" BorderThickness="0,0.5,0,0.5">
<GridViewRowPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
<Style x:Key="CustomHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Padding" Value="2,0,2,0"/>
</Style>
</UserControl.Resources>
<Grid x:Name="GridViewControl" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="34"/>
</Grid.RowDefinitions>
<ListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<GridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#id}"/>
<GridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" CellTemplateSelector="{DynamicResource myControlTemplateSelector}" />
<GridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#data_type}"/>
<GridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>

Resources