WPF: Scroll Itemcontrol Content Fixed Header - wpf

Is it possible to do something like this with WPF's ItemsControl: Demo
I am trying to freeze the GroupedItems rather than the GridView Columns.
Resources:
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
ListView:
<ListView Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Code behind:
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a < 100; a++)
{
Random rnd = new Random();
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
});
}
this.DataContext = dataCollection;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}

My solution uses a TextBlock overlay that shares the group header style. The positioning and correct HitTesting is the tricky part, but I'm quite confident this does not break for small changes in layout or logic.
I was not sure if you want to hide the ColumnHeader or not, but this is easy and doesn't need any other adjustments than what is depicted here.
Code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication1
{
public partial class FreezingGroupHeader : UserControl
{
private double _listviewHeaderHeight;
private double _listviewSideMargin;
public FreezingGroupHeader()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
Random rnd = new Random();
for (var a = 0; a < 100; a++)
{
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(
new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
}
);
}
this.DataContext = dataCollection;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Position frozen header
GetListViewMargins(this.listview1);
Thickness margin = this.frozenGroupHeader.Margin;
margin.Top = _listviewHeaderHeight;
margin.Right = SystemParameters.VerticalScrollBarWidth + _listviewSideMargin;
margin.Left = _listviewSideMargin;
this.frozenGroupHeader.Margin = margin;
UpdateFrozenGroupHeader();
}
private void listview1_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
UpdateFrozenGroupHeader();
}
/// <summary>
/// Sets text and visibility of frozen header
/// </summary>
private void UpdateFrozenGroupHeader()
{
if (listview1.HasItems)
{
// Text of frozenGroupHeader
GroupItem group = GetFirstVisibleGroupItem(this.listview1);
if (group != null)
{
object data = group.Content;
this.frozenGroupHeader.Text = data.GetType().GetProperty("Name").GetValue(data, null) as string; // slight hack
}
this.frozenGroupHeader.Visibility = Visibility.Visible;
}
else
this.frozenGroupHeader.Visibility = Visibility.Collapsed;
}
/// <summary>
/// Sets values that will be used in the positioning of the frozen header
/// </summary>
private void GetListViewMargins(ListView listview)
{
if (listview.HasItems)
{
object o = listview.Items[0];
ListViewItem firstItem = (ListViewItem)listview.ItemContainerGenerator.ContainerFromItem(o);
if (firstItem != null)
{
GroupItem group = FindUpVisualTree<GroupItem>(firstItem);
Point p = group.TranslatePoint(new Point(0, 0), listview);
_listviewHeaderHeight = p.Y; // height of columnheader
_listviewSideMargin = p.X; // listview borders
}
}
}
/// <summary>
/// Gets the first visible GroupItem in the listview
/// </summary>
private GroupItem GetFirstVisibleGroupItem(ListView listview)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(listview, new Point(5, _listviewHeaderHeight + 5));
GroupItem group = FindUpVisualTree<GroupItem>(hitTest.VisualHit);
return group;
}
/// <summary>
/// walk up the visual tree to find object of type T, starting from initial object
/// http://www.codeproject.com/Tips/75816/Walk-up-the-Visual-Tree
/// </summary>
private static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
}
}
Xaml:
<UserControl x:Class="WpfApplication1.FreezingGroupHeader"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<UserControl.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="GroupHeaderStyle1" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Beige" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Resources>
<Grid>
<ListView x:Name="listview1" Grid.Column="0" ItemsSource="{Binding Source={StaticResource data}}" ScrollViewer.ScrollChanged="listview1_ScrollChanged" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Col 1" DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn Header="Col 2" DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn Header="Col 3" DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource GroupHeaderStyle1}" Text="{Binding Name, StringFormat={}{0}}" />
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<TextBlock x:Name="frozenGroupHeader" Style="{StaticResource GroupHeaderStyle1}" VerticalAlignment="Top"/>
</Grid>
</UserControl>

As i just ran into a similar issue and the 'hack-ish' solution did not fit my needs and i generally dont like 'hack-ish' stuff in production environments, i developed a generic solution to this which i'd like to share.
The attached Class has following key-features:
MVVM compatible
no Code-Behind
compatible with ListView, GridView, ItemsControl, even static xaml! - should work with anything using a ScollViewer ...
Uses attached property to declare the group item
xaml usage (just your inner ControlTemplate):
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" local:StickyScrollHeader.AttachToControl="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Grid}}">
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
The Class (put anywhere, add xaml-namespace if necessary):
public static class StickyScrollHeader
{
public static FrameworkElement GetAttachToControl(FrameworkElement obj)
{
return (FrameworkElement)obj.GetValue(AttachToControlProperty);
}
public static void SetAttachToControl(FrameworkElement obj, FrameworkElement value)
{
obj.SetValue(AttachToControlProperty, value);
}
private static ScrollViewer FindScrollViewer(FrameworkElement item)
{
FrameworkElement treeItem = item;
FrameworkElement directItem = item;
while (treeItem != null)
{
treeItem = VisualTreeHelper.GetParent(treeItem) as FrameworkElement;
if (treeItem is ScrollViewer)
{
return treeItem as ScrollViewer;
}
else if (treeItem is ScrollContentPresenter)
{
return (treeItem as ScrollContentPresenter).ScrollOwner;
}
}
while (directItem != null)
{
directItem = directItem.Parent as FrameworkElement;
if (directItem is ScrollViewer)
{
return directItem as ScrollViewer;
}
else if (directItem is ScrollContentPresenter)
{
return (directItem as ScrollContentPresenter).ScrollOwner;
}
}
return null;
}
private static ScrollContentPresenter FindScrollContentPresenter(FrameworkElement sv)
{
int childCount = VisualTreeHelper.GetChildrenCount(sv);
for (int i = 0; i < childCount; i++)
{
if (VisualTreeHelper.GetChild(sv, i) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
for (int i = 0; i < childCount; i++)
{
if (FindScrollContentPresenter(VisualTreeHelper.GetChild(sv, i) as FrameworkElement) is FrameworkElement child && child is ScrollContentPresenter)
{
return child as ScrollContentPresenter;
}
}
return null;
}
public static readonly DependencyProperty AttachToControlProperty =
DependencyProperty.RegisterAttached("AttachToControl", typeof(FrameworkElement), typeof(StickyScrollHeader), new PropertyMetadata(null, (s, e) =>
{
try
{
if (!(s is FrameworkElement targetControl))
{ return; }
Canvas.SetZIndex(targetControl, 999);
ScrollViewer sv;
FrameworkElement parent;
if (e.OldValue is FrameworkElement oldParentControl)
{
ScrollViewer oldSv = FindScrollViewer(oldParentControl);
parent = oldParentControl;
oldSv.ScrollChanged -= Sv_ScrollChanged;
}
if (e.NewValue is FrameworkElement newParentControl)
{
sv = FindScrollViewer(newParentControl);
parent = newParentControl;
sv.ScrollChanged += Sv_ScrollChanged;
}
void Sv_ScrollChanged(object sender, ScrollChangedEventArgs sce)
{
if (!parent.IsVisible) { return; }
try
{
ScrollViewer isv = sender as ScrollViewer;
ScrollContentPresenter scp = FindScrollContentPresenter(isv);
var relativeTransform = parent.TransformToAncestor(scp);
Rect parentRenderRect = relativeTransform.TransformBounds(new Rect(new Point(0, 0), parent.RenderSize));
Rect intersectingRect = Rect.Intersect(new Rect(new Point(0, 0), scp.RenderSize), parentRenderRect);
if (intersectingRect != Rect.Empty)
{
TranslateTransform targetTransform = new TranslateTransform();
if (parentRenderRect.Top < 0)
{
double tempTop = (parentRenderRect.Top * -1);
if (tempTop + targetControl.RenderSize.Height < parent.RenderSize.Height)
{
targetTransform.Y = tempTop;
}
else if (tempTop < parent.RenderSize.Height)
{
targetTransform.Y = tempTop - (targetControl.RenderSize.Height - intersectingRect.Height);
}
}
else
{
targetTransform.Y = 0;
}
targetControl.RenderTransform = targetTransform;
}
}
catch { }
}
}
catch { }
}));
}
Hope this also helps others running into this issue ;)

This solution is not great, and it's hack-ish, but it will basically do what you want. I made the listview headers invisible, put a textblock above the listview, and set the text value to the groupitem of the first visible item in the listbox. Hacky, but it's the best I came up with.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<String> colList1 = new List<string>() { "Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7" };
List<String> colList2 = new List<string>() { "1", "2", "3", "4", "5", "6" };
ObservableCollection<Data> dataCollection = new ObservableCollection<Data>();
for (var a = 0; a < 100; a++)
{
Random rnd = new Random();
int min = rnd.Next(5000);
int rnd1 = rnd.Next(0, 6);
int rnd2 = rnd.Next(0, 5);
dataCollection.Add(
new Data()
{
Date = DateTime.Now.AddMinutes(min).ToString("hh:MM tt"),
Col1 = colList1[rnd2],
Col2 = String.Format("Col2: {0}", "X"),
Col3 = colList2[rnd2]
}
);
}
this.DataContext = dataCollection;
}
public class Data
{
public string Date { get; set; }
public string Col1 { get; set; }
public string Col2 { get; set; }
public string Col3 { get; set; }
}
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, new Point(5, 5));
System.Windows.Controls.ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
Head.Text = ((Data)item.Content).Date;
}
}
System.Windows.Controls.ListViewItem GetListViewItemFromEvent(object sender, object originalSource)
{
DependencyObject depObj = originalSource as DependencyObject;
if (depObj != null)
{
// go up the visual hierarchy until we find the list view item the click came from
// the click might have been on the grid or column headers so we need to cater for this
DependencyObject current = depObj;
while (current != null && current != grid)
{
System.Windows.Controls.ListViewItem ListViewItem = current as System.Windows.Controls.ListViewItem;
if (ListViewItem != null)
{
return ListViewItem;
}
current = VisualTreeHelper.GetParent(current);
}
}
return null;
}
}
XAML:
<Window x:Class="header.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="auto" SizeToContent="Width">
<Window.Resources>
<CollectionViewSource x:Key="data" Source="{Binding}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock Name="Head" Grid.Row="0"/>
<ListView Name="grid" Grid.Column="0" Grid.Row="1" ItemsSource="{Binding Source={StaticResource data}}" Height="300" ScrollViewer.ScrollChanged="grid_ScrollChanged">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}">
<GridView.Columns>
<GridViewColumn DisplayMemberBinding="{Binding Col1}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col2}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Col3}" Width="100"/>
</GridView.Columns>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Background="Beige" FontWeight="Bold" Text="{Binding Path=Name, StringFormat={}{0}}"/>
</Grid>
<DockPanel Grid.Row="1">
<ItemsPresenter Grid.Row="2"></ItemsPresenter>
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</StackPanel>
</Grid>
EDIT: Fixed ScrollChanged event.
private void grid_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (grid.Items.Count > 0)
{
Point point = new Point(5, 5);
foreach(Data lvItem in grid.Items)
{
HitTestResult hitTest = VisualTreeHelper.HitTest(grid, point);
ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;
if (item != null)
{
Data value = ((Data)item.Content);
Head.Text = ((Data)item.Content).Date;
break;
}
else
{
point.X += 5;
point.Y += 5;
}
}
}
}

Related

PropertyGroupDescription CustomSort is missing the ItemCount in item X

Preamble:
First I have searched stackoverflow and all of the topics pertaining to sorting grouped datagrids do not apply to this question. In fact none of the answers actually show how to sort by group count without using 3rd partly libraries.
The Problem:
I am trying to sort my datagrid groups by count by overriding the CustomSort property of the PropertyGroupDescription. When I assign a CustomSort method to the GroupDescription, the Compare function's object x CollectionViewGroup always has an ItemCount == 0.
Here is my sample xaml which is mostly taken from Microsoft's help:
<Window
x:Class="GroupedSorting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GroupedSorting"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid
Name="dg"
Grid.Row="0"
ItemsSource="{Binding ItemVMs}">
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="#FF112255" BorderBrush="#FF002255" Foreground="#FFEEEEEE" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<!-- Style for groups under the top level. -->
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<DockPanel Background="LightBlue">
<TextBlock Text="{Binding Path=Name, Converter={StaticResource completeConverter}}" Foreground="Blue" Margin="30,0,0,0" Width="100"/>
<TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/>
</DockPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
And here is the code behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace GroupedSorting
{
public partial class MainWindow : Window
{
public ObservableCollection<MyViewModel> ItemVMs { get; set; } = new ObservableCollection<MyViewModel>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
var r = new Random();
for (int i = 0; i < 10; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 1",
MyIndex = r.Next()
}); ;
}
for (int i = 0; i < 5; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 2",
MyIndex = r.Next()
});
}
for (int i = 0; i < 1; i++)
{
ItemVMs.Add(new MyViewModel()
{
Name = "Group 3",
MyIndex = r.Next()
});
}
List<MyViewModel> sortedItems = new List<MyViewModel>();
var groups = ItemVMs.GroupBy(x => x.Name);
foreach (var group in groups.OrderByDescending(x => x.Count()))
{
sortedItems.AddRange(group);
}
ItemVMs.Clear();
ItemVMs = new ObservableCollection<MyViewModel>(sortedItems);
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView;
var gd = new PropertyGroupDescription(nameof(MyViewModel.Name));
gd.CustomSort = new GroupComparer();
sourceView.GroupDescriptions.Add(gd);
sourceView.Refresh();
}
}
public class MyViewModel
{
public string Name { get; set; }
public int MyIndex { get; set; }
}
public class GroupComparer : System.Collections.IComparer
{
public int Compare(object x, object y)
{
if (!(x is CollectionViewGroup xViewGroup))
return 0;
if (!(y is CollectionViewGroup yViewGroup))
return 0;
Debug.WriteLine($"{xViewGroup.Name} {xViewGroup.ItemCount}, {yViewGroup.Name} {yViewGroup.ItemCount}");
if (xViewGroup.ItemCount < yViewGroup.ItemCount)
return 1;
else if (xViewGroup.ItemCount > yViewGroup.ItemCount)
return -1;
return 0;
}
}
}
When the code is run, the xViewGroup.ItemCount is always equal to 0 causing the sort method to fail.
Cast the ICollectionView returned from CollectionViewSource.GetDefaultView to a ListCollectionView and set the CustomSort property of this one to your custom IComparer:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var sourceView = CollectionViewSource.GetDefaultView(dg.ItemsSource) as ListCollectionView;
var gd = new PropertyGroupDescription(nameof(MyViewModel.Name));
sourceView.CustomSort = new GroupComparer();
sourceView.GroupDescriptions.Add(gd);
sourceView.Refresh();
}

add dynamic Tab Item having data grid control

I have tab control In wpf Application. I want to add dynamic Tab Item having data grid control ? Any Solutions. Thanks In Advance.
please try the next solution:
Xaml Code (just uncomment gridview code if needed):
<Window x:Class="TabControlSOHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControlSoHelpAttempt="clr-namespace:TabControlSOHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<tabControlSoHelpAttempt:TabControlViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0" ItemsSource="{Binding Customers}" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type tabControlSoHelpAttempt:Customer}">
<TextBlock>
<Run Text="Customer:"></Run>
<Run Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"></Run>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type tabControlSoHelpAttempt:Customer}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<!--<ListView Grid.Row="1" ItemsSource="{Binding CustomerDataCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Book Name" DisplayMemberBinding="{Binding BookName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
<GridViewColumn Header="Keeping From" DisplayMemberBinding="{Binding KeepingFrom, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
</GridView>
</ListView.View>
</ListView>-->
<DataGrid Grid.Row="1" ItemsSource="{Binding CustomerDataCollection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Book Name" Binding="{Binding BookName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></DataGridTextColumn>
<DataGridTextColumn Header="Keeping From" Binding="{Binding KeepingFrom, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" VerticalAlignment="Bottom" Command="{Binding RemoveCustomer}">Remove Customer</Button>
<Button Grid.Column="1" VerticalAlignment="Bottom" Command="{Binding AddCustomer}">Add Customer</Button></Grid>
</Grid></Window>
View Models and models:
public class TabControlViewModel:BaseObservableObject
{
private ICommand _addCommand;
private Customer _selectedCustomer;
private ICommand _removeCommand;
public TabControlViewModel()
{
//here you can inject a model which will be able
//to support the initial data for the Customers colection,
//and will inform user if a customer was added or removed.
//On each model changes you can add a new custome object into Customers collection,
//since this collection is ObservableCollection, UI will be updated imidiatelly
Customers = new ObservableCollection<Customer>
{
new Customer
{
Name = "John",
CustomerDataCollection = new ObservableCollection<CustomerData>
{
new CustomerData
{
BookName = "Uncle Vania",
KeepingFrom = DateTime.Today,
},
new CustomerData
{
BookName = "Anna Karenine",
KeepingFrom = DateTime.Today,
}
}
},
new Customer
{
Name = "Tom",
CustomerDataCollection = new ObservableCollection<CustomerData>
{
new CustomerData
{
BookName = "War and Peace",
KeepingFrom = DateTime.Today,
},
new CustomerData
{
BookName = "Alice's Adventures in Wonderland",
KeepingFrom = DateTime.Today,
}
}
},
};
}
public ObservableCollection<Customer> Customers { get; set; }
public ICommand AddCustomer
{
get { return _addCommand ?? (_addCommand = new RelayCommand(AddCommandMethod)); }
}
private void AddCommandMethod()
{
Customers.Add(new Customer());
SelectedCustomer = Customers.LastOrDefault();
}
public ICommand RemoveCustomer
{
get { return _removeCommand ?? (_removeCommand = new RelayCommand(RemoveCommandMethod)); }
}
private void RemoveCommandMethod()
{
if(SelectedCustomer == null) return;
Customers.Remove(SelectedCustomer);
SelectedCustomer = Customers.LastOrDefault();
}
public Customer SelectedCustomer
{
get { return _selectedCustomer; }
set
{
_selectedCustomer = value;
OnPropertyChanged();
}
}
}
public class Customer:BaseObservableObject
{
public Customer()
{
Name = "Enter Customer Name";
CustomerDataCollection = new ObservableCollection<CustomerData>();
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public ObservableCollection<CustomerData> CustomerDataCollection { get; set; }
}
public class CustomerData:BaseObservableObject
{
private string _bookName;
private DateTime _qty;
public string BookName
{
get { return _bookName; }
set
{
_bookName = value;
OnPropertyChanged();
}
}
public DateTime KeepingFrom
{
get { return _qty; }
set
{
_qty = value;
OnPropertyChanged();
}
}
}
I'll be glad to help if there will be problems with the code, just let me know about this.
Regards.

How to Scroll to a new item added to a list view in MVVM Light

I've got a project where there is a ListView and when the User clicks the New button the new Object is added to the bottom of the ListView. I've tried using a Content Style class but that didn't work. I just need something that will scroll to the selected item. Below is my code:
View:
<ListView Margin="103,0,0,10" ScrollViewer.CanContentScroll="True" Height="87" SelectedItem="{Binding SelectedSession, Mode=TwoWay}" ItemsSource="{Binding SessionCollection}">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Session Name" Width="180" DisplayMemberBinding="{Binding SessionName, Mode=TwoWay}" />
<GridViewColumn Header="Operator Name" Width="180" DisplayMemberBinding="{Binding OperatorName, Mode=TwoWay}"/>
<GridViewColumn Header="Session Date" Width="180" DisplayMemberBinding="{Binding SessionDate, Mode=TwoWay}"/>
</GridView>
</ListView.View>
</ListView>
View Model code for the New Button :
public void NewSession()
{
Session newSession = new Session();
SessionCollection.Add(newSession);
SelectedSession = newSession;
SessionDate = DateTime.Now;
StartTime = DateTime.Now;
EndTime = DateTime.Now;
ProjectManager.Instance.CurrentSession = null;
}
public ObservableCollection<Session> SessionCollection
{
get
{
if (currentDatabaseProj.Sessions == null)
{
return currentDatabaseProj.Sessions;
}
else
{
return currentDatabaseProj.Sessions;
}
}
private set
{
currentDatabaseProj.Sessions = value;
IsNewProjClicked = true;
this.RaisePropertyChanged("SessionCollection");
}
}
One simple handler in the code behind should do the trick
(i simplify your code to made it clear)
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView x:Name="listView" Margin="10" ScrollViewer.CanContentScroll="True" SelectedItem="{Binding SelectedSession, Mode=TwoWay}" ItemsSource="{Binding SessionCollection}">
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Session Name" Width="180" DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
<Button Click="UIElement_NewElementHandler" Grid.Row="1" Content="Add Item" Command="{Binding AddItem}"></Button>
</Grid>
and in the view's code behind add the following handler :
private void UIElement_NewElementHandler(object sender, RoutedEventArgs routedEventArgs)
{
var border = VisualTreeHelper.GetChild(listView, 0) as Decorator;
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
ps: scrolling down to a new added item is something ViewRelated, none of the View Model Properties are used in the codebehind, so i believe you are not violating any mvvm rule by doing that.
Working example with behavior
xaml
<Window x:Class="ListView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:listView="clr-namespace:ListView"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Content="Add New" Grid.Row="0" Click="Button_Click"></Button>
<ListView Grid.Row="1" ItemsSource="{Binding MySessionList}" SelectedItem="{Binding SelectedSession, Mode=TwoWay}">
<i:Interaction.Behaviors>
<listView:ScrollIntoViewBehavior/>
</i:Interaction.Behaviors>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding Name, Mode=TwoWay}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
cs
public partial class MainWindow : Window
{
private Viewmodel _data;
public MainWindow()
{
InitializeComponent();
_data = new Viewmodel();
this.DataContext = _data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_data.AddNew();
}
}
public class Viewmodel : INotifyPropertyChanged
{
private MySession _selectedSession;
public ObservableCollection<MySession> MySessionList { get; set; }
public Viewmodel()
{
this.MySessionList = new ObservableCollection<MySession>();
//fill with some data
for (int i = 0; i < 20; i++)
{
this.MySessionList.Add(new MySession(){Name = "Index : " + i});
}
}
public MySession SelectedSession
{
get { return _selectedSession; }
set
{
_selectedSession = value;
OnPropertyChanged();
}
}
//should be a Command for MVVM but for quick and dirty
public void AddNew()
{
var toAdd = new MySession() {Name = "New Added " + DateTime.Now.ToLongTimeString()};
this.MySessionList.Add(toAdd);
this.SelectedSession = toAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MySession
{
public string Name { get; set; }
}
//ADD REFERENCE: System.Windows.Interactivity
public class ScrollIntoViewBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var ctrl = sender as ListBox;
if (ctrl == null)
return;
if (ctrl.SelectedItem != null)
{
ctrl.Dispatcher.BeginInvoke(
DispatcherPriority.Render,
new Action(() =>
{
ctrl.UpdateLayout();
ctrl.ScrollIntoView(ctrl.SelectedItem);
}));
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
}

How to use ColumnDefinitions.Add correctly?

I copied an example about dock panel from "WPF 4.5 Unleashed" ch.5. In the example the dock is set to be in the right of the grid So when in the code behind layer0.ColumnDefinitions.Add(column1CloneForLayer0); called, a column with content column1CloneForLayer0 will be created in the right side of the grid.
In my project I almost did the same thing, but when I call the function to add a column with some content, some empty space's created at the right side.. which is not as expected.So what's the right thing to do?
1.How to Add a column in the left side of a grid?
2.In the example the column was created yet why it's empty? Maybe Do i need to set the Z Index?
Update
I added something similar in the original example in the book and get wrong result. Below is the the source codes. I just added a leftToolBox in the left.
https://www.dropbox.com/sh/61hm139j77kz9k1/AACKqhG5uXFkQgnt8fWi4NvNa?dl=0
Update2
Relevant codes: In this case I just add a stackpanel with button in the left side and and click to call DocakPane(3), instead of giving the new column in the left, it creates column in the right.
public void DockPane(int paneNumber)
{
if (paneNumber == 1)
{
pane1Button.Visibility = Visibility.Collapsed;
pane1PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column1CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 2 is docked:
if (pane2Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else if (paneNumber == 2)
{
pane2Button.Visibility = Visibility.Collapsed;
pane2PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
// Add the cloned column to layer 0:
layer0.ColumnDefinitions.Add(column2CloneForLayer0);
// Add the cloned column to layer 1, but only if pane 1 is docked:
if (pane1Button.Visibility == Visibility.Collapsed) layer1.ColumnDefinitions.Add(column2CloneForLayer1);
}
else
{
leftpane1Button.Visibility = Visibility.Collapsed;
pane3PinImage.Source = new BitmapImage(new Uri("pin.gif", UriKind.Relative));
layer0.ColumnDefinitions.Add(testcol);
}
}
Based on the sample project provided, here is a re-write of the same using MVVM and most of the problem related to hard-coding would are gone. It is not pure MVVM but it is mostly MVVM to get started with.
start by defining ViewModels
a class to represent the dock pane
class DockablePaneVM : ViewModelBase
{
public DockablePaneVM(DockHostVM host)
{
Host = host;
}
private string _title;
public string Title
{
get { return _title; }
set
{
_title = value;
RaisePropertyChanged("Title");
}
}
private bool _isPinned;
public bool IsPinned
{
get { return _isPinned; }
set
{
_isPinned = value;
Host.PinModeChanged(this);
RaisePropertyChanged("IsPinned");
}
}
private object _content;
public object Content
{
get { return _content; }
set
{
_content = value;
RaisePropertyChanged("Content");
}
}
public DockHostVM Host { get; private set; }
public Dock Dock { get; set; }
}
host for the dockpanes, I have used collectionview for simplifying the location
class DockHostVM : ViewModelBase
{
public DockHostVM()
{
Panes = new ObservableCollection<DockablePaneVM>();
LeftPanes = new CollectionViewSource() { Source = Panes }.View;
RightPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingLeftPanes = new CollectionViewSource() { Source = Panes }.View;
FlotingRightPanes = new CollectionViewSource() { Source = Panes }.View;
LeftPanes.Filter = o => Filter(o, Dock.Left, true);
RightPanes.Filter = o => Filter(o, Dock.Right, true);
FlotingLeftPanes.Filter = o => Filter(o, Dock.Left, false);
FlotingRightPanes.Filter = o => Filter(o, Dock.Right, false);
}
private bool Filter(object obj, Dock dock, bool isPinned)
{
DockablePaneVM vm = obj as DockablePaneVM;
return vm.Dock == dock && vm.IsPinned == isPinned;
}
public ObservableCollection<DockablePaneVM> Panes { get; set; }
public ICollectionView LeftPanes { get; set; }
public ICollectionView RightPanes { get; set; }
public ICollectionView FlotingLeftPanes { get; set; }
public ICollectionView FlotingRightPanes { get; set; }
private object _content;
public object Content
{
get { return _content; }
set { _content = value; }
}
public void PinModeChanged(DockablePaneVM paneVM)
{
LeftPanes.Refresh();
RightPanes.Refresh();
FlotingLeftPanes.Refresh();
FlotingRightPanes.Refresh();
}
//sample generator
public static DockHostVM GetSample()
{
DockHostVM vm = new DockHostVM();
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Solution Explorer", Content = new SolutionExplorerVM(), Dock = Dock.Right });
vm.Panes.Add(new DockablePaneVM(vm) { Title = "Toolbox", Content = new ToolBoxVM(), Dock = Dock.Right });
return vm;
}
}
then styles, to give provide the view for the classes
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<DataTemplate DataType="{x:Type l:DockablePaneVM}">
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Padding="8,4"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
Background="{DynamicResource {x:Static SystemColors.ActiveCaptionBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.ActiveCaptionTextBrushKey}}" />
<ToggleButton IsChecked="{Binding IsPinned}"
Grid.Column="1">
<Image Name="pinImage"
Source="pinHorizontal.gif" />
</ToggleButton>
<ContentControl Content="{Binding Content}"
Grid.Row="1"
Grid.ColumnSpan="2" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsPinned}"
Value="True">
<Setter Property="Source"
TargetName="pinImage"
Value="pin.gif" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Title}" />
</Style>
<Style TargetType="TabItem"
x:Key="FloterItem"
BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="90" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TabControl">
<Setter Property="l:TabControlHelper.IsLastItemSelected"
Value="True" />
<Style.Triggers>
<Trigger Property="HasItems"
Value="false">
<Setter Property="Visibility"
Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TabControl"
x:Key="AutoResizePane"
BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="false">
<Setter Property="Width"
Value="23" />
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type l:ToolBoxVM}">
<ListBox Padding="10"
Grid.Row="1">
<ListBoxItem>Button</ListBoxItem>
<ListBoxItem>CheckBox</ListBoxItem>
<ListBoxItem>ComboBox</ListBoxItem>
<ListBoxItem>Label</ListBoxItem>
<ListBoxItem>ListBox</ListBoxItem>
</ListBox>
</DataTemplate>
<DataTemplate DataType="{x:Type l:SolutionExplorerVM}">
<TreeView Grid.Row="2">
<TreeViewItem Header="My Solution" IsExpanded="True">
<TreeViewItem Header="Project #1" />
<TreeViewItem Header="Project #2" />
<TreeViewItem Header="Project #3" />
</TreeViewItem>
</TreeView>
</DataTemplate>
</ResourceDictionary>
I have also created some dummy classes to represent the toolbox and solution explorer
also a helper class to improve the usability of tab control which i have used to host the dockpanes
class TabControlHelper
{
public static bool GetIsLastItemSelected(DependencyObject obj)
{
return (bool)obj.GetValue(IsLastItemSelectedProperty);
}
public static void SetIsLastItemSelected(DependencyObject obj, bool value)
{
obj.SetValue(IsLastItemSelectedProperty, value);
}
// Using a DependencyProperty as the backing store for IsLastItemSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsLastItemSelectedProperty =
DependencyProperty.RegisterAttached("IsLastItemSelected", typeof(bool), typeof(TabControlHelper), new PropertyMetadata(false, OnIsLastItemSelected));
private static void OnIsLastItemSelected(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TabControl tc = d as TabControl;
tc.Items.CurrentChanged += (ss, ee) =>
{
if (tc.SelectedIndex < 0 && tc.Items.Count > 0)
tc.SelectedIndex = 0;
};
}
}
this will keep an item selected any time, in this project it will be used when a dock pane is pinned/unpinned
now the main window, note that I have bounded the dock Panes to 4 tab controls, left, right, left float & right float
<Window x:Class="VisualStudioLikePanes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="500"
Width="800"
WindowStartupLocation="CenterScreen"
xmlns:l="clr-namespace:VisualStudioLikePanes">
<Window.DataContext>
<ObjectDataProvider MethodName="GetSample"
ObjectType="{x:Type l:DockHostVM}" />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Menu>
<MenuItem Header="FILE" />
...
<MenuItem Header="HELP" />
</Menu>
<DockPanel LastChildFill="True"
Grid.Row="1">
<Border Width="23"
DockPanel.Dock="Left"
Visibility="{Binding Visibility, ElementName=LeftFloter}" />
<Border Width="23"
DockPanel.Dock="Right"
Visibility="{Binding Visibility, ElementName=RightFloter}" />
<TabControl ItemsSource="{Binding LeftPanes}"
DockPanel.Dock="Left" />
<TabControl ItemsSource="{Binding RightPanes}"
DockPanel.Dock="Right" />
<Grid Name="layer0">
... page content
</Grid>
</DockPanel>
<TabControl ItemsSource="{Binding FlotingLeftPanes}"
Grid.Row="1"
HorizontalAlignment="Left"
TabStripPlacement="Left"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="LeftFloter" />
<TabControl ItemsSource="{Binding FlotingRightPanes}"
Grid.Row="1"
HorizontalAlignment="Right"
TabStripPlacement="Right"
Style="{StaticResource AutoResizePane}"
ItemContainerStyle="{StaticResource FloterItem}"
x:Name="RightFloter" />
</Grid>
</Window>
result is your expected behavior with MVVM approach, adding new panel is easy as Panes.Add(new DockablePaneVM(vm) { Title = "Left Toolbox", Content = new ToolBoxVM() }); rest is handled.
Demo
download the working sample VisualStudioLikePanes.zip

How to apply multiple filters on Listview

My DataModel :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
namespace LogViewerApplication
{
public class LogRead
{
string _path;
List<string> _strList;
public List<string> StrList
{
get { return _strList; }
}
public LogRead()
{
_path = #"D:\Log viewer (project)\Test_log.txt";
}
/// <summary>
/// To read the file and then to store the records in a List.
/// </summary>
public void GetFile()
{
//_path = #"D:\Log viewer (project)\presentation_log.txt";
string _pattern = #"^\d{2}/\d{2}/\d{4}";
_strList = new List<string>();
using (StreamReader objReader = new StreamReader(_path))
{
string sLine = "";
while ((sLine = objReader.ReadLine()) != null)
{
_strList.Add(sLine);
}
}
int j;
string sub;
for (int k = 0; k < _strList.Count; k++)
{
if (!Regex.IsMatch(_strList[k], _pattern))
{
j = k;
sub = _strList[k];
_strList[j - 1] = _strList[j - 1] + " " + sub;
_strList.RemoveAt(k);
//Console.WriteLine(strlist[k]);
//Console.WriteLine();
k--;
}
}
}
}
public enum Severe { Normal, Debug, Critical }
public class LogViewerModel
{
public DateTime Datetime { get; set; }
public string Source { get; set; }
public Severe Severity { get; set; }
public string Message { get; set; }
}
}
My ViewModel :
namespace LogViewerApplication
{
class LogViewerViewModel:INotifyPropertyChanged
{
ObservableCollection<LogViewerModel> _logViewerModelCollection;
LogRead _logRead;
ICollectionView _viewSource;
//GroupFilter gf;
public ICollectionView ViewSource
{
get
{
if(_viewSource==null)
{
_viewSource = CollectionViewSource.GetDefaultView(_logViewerModelCollection);
}
return _viewSource;
}
}
#region Commands
#region SetColorCommand
private RelayCommand _setColorCommand;
public ICommand SetColorCommand
{
get
{
if (_setColorCommand == null)
{
_setColorCommand = new RelayCommand(SetColor);
}
return _setColorCommand;
}
}
private void SetColor(Object param)
{
if (_norm && !_dbg && !_crtcl)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Normal;
};
}
else if (_dbg && !_norm && !_crtcl)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Debug;
};
}
else if (_crtcl && !_norm && !_dbg)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Critical;
};
}
else if (_norm && _dbg && !_crtcl)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Normal || ((LogViewerModel)item).Severity == Severe.Debug;
};
}
else if (_norm && _crtcl && !_dbg)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Normal || ((LogViewerModel)item).Severity == Severe.Critical;
};
}
else if (_dbg && _crtcl && !_norm)
{
_viewSource.Filter = delegate(object item)
{
return ((LogViewerModel)item).Severity == Severe.Debug || ((LogViewerModel)item).Severity == Severe.Critical;
};
}
else if (_norm && _dbg && _crtcl)
{
_viewSource.Filter = delegate(object item)
{
return true;
};
}
else
{
_viewSource.Filter = delegate(object item)
{
return false;
};
}
}
#endregion
#region SearchCommand
private RelayCommand _searchCommand;
public ICommand SearchCommand
{
get
{
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(Search);
}
return _searchCommand;
}
}
private void Search(object param)
{
string str = param as string;
if (_searchSource == "Source")
{
_viewSource.Filter = delegate(object item)
{
if (string.IsNullOrEmpty(str))
{
return true;
}
else
{
int index = ((LogViewerModel)item).Source.IndexOf(str, 0, StringComparison.InvariantCultureIgnoreCase);
return index > -1;
}
//return ((LogViewerModel)item).Severity == Severe.Normal;
};
}
else if (_searchSource == "Message")
{
_viewSource.Filter = delegate(object item)
{
if (string.IsNullOrEmpty(str))
{
return true;
}
else
{
int index = ((LogViewerModel)item).Message.IndexOf(str, 0, StringComparison.InvariantCultureIgnoreCase);
return index > -1;
}
//return ((LogViewerModel)item).Severity == Severe.Normal;
};
}
else
{
_viewSource.Filter = delegate(object item)
{
return true;
};
}
}
#endregion
#region DateFilterCommand
private RelayCommand _dateFilterCommand;
public ICommand DateFilterCommand
{
get
{
if (_dateFilterCommand == null)
{
_dateFilterCommand = new RelayCommand(DateFilter);
}
return _dateFilterCommand;
}
}
private void DateFilter(object param)
{
string _startDate;
string _endDate;
if (string.IsNullOrEmpty(_startHour) && string.IsNullOrEmpty(_startMinute) && string.IsNullOrEmpty(_endHour) && string.IsNullOrEmpty(_endMinute))
{
_startDate = _startDay + "-" + StartMonth + "-" + _startYear;
_endDate = _endDay + "-" + _endMonth + "-" + _endYear + " " + "23" + ":" + "59" + ":" + "59";
try
{
DateTime startDate = Convert.ToDateTime(_startDate);
DateTime endDate = Convert.ToDateTime(_endDate);
_viewSource.Filter = delegate(object item)
{
return (((LogViewerModel)item).Datetime >= startDate) && (((LogViewerModel)item).Datetime <= endDate);
};
}
catch
{
MessageBox.Show("Please Enter a valid DATE range..!!!!");
}
}
else
{
_startDate = _startDay + "-" + StartMonth + "-" + _startYear + " " + _startHour + ":" + _startMinute;
_endDate = _endDay + "-" + _endMonth + "-" + _endYear + " " + _endHour + ":" + _endMinute + ":" + "59";
try
{
DateTime startDate = Convert.ToDateTime(_startDate);
DateTime endDate = Convert.ToDateTime(_endDate);
_viewSource.Filter = delegate(object item)
{
return (((LogViewerModel)item).Datetime >= startDate) && (((LogViewerModel)item).Datetime <= endDate);
};
}
catch
{
MessageBox.Show("Please Enter a valid DATE range..!!!!");
}
}
}
#endregion
#endregion
public LogViewerViewModel()
{
_logRead = new LogRead();
_logRead.GetFile();
_logViewerModelCollection= new ObservableCollection<LogViewerModel>();
loaddata();
//gf = new GroupFilter();
}
/// <summary>
/// It is used to split the string property wise and to store the data
/// into an ObservableCollection.
/// </summary>
public void loaddata()
{
int listSize = _logRead.StrList.Count;
DateTime dateTime;
string source;
Severe severity;
string message;
for (int k = 0; k < listSize; k++)
{
int flag = 0;
string str = _logRead.StrList[k];
//To check if a particular log entry is valid or not
//on the basis of "Severity".
foreach (string s in Enum.GetNames(typeof(Severe)))
{
bool check = Regex.IsMatch(str, string.Format(#"\b{0}\b", Regex.Escape(s)));
if (check)
{
flag = 1;
break;
}
}
//If log entry is Valid then execute the following Code.
if (flag == 1)
{
//dateTime = Convert.ToDateTime(str.Substring(0, 18));
dateTime = DateTime.ParseExact(str.Substring(0, 19), "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
string lft = str.Substring(21, str.Length - 21);
int c = 4;
string delimStr = ":";
char[] delimiter = delimStr.ToCharArray();
string[] x = lft.Split(delimiter, c);
int size = x.Length;
if (size == 2)
{
source = x[0];
severity =(Severe)Enum.Parse(typeof(Severe),x[1].Trim());
message = " ";
AssignData(dateTime, source, severity, message);
}
else
{
for (int i = 0; i < size; i++)
{
if (x[i].Length == 1)
{
while (i < size - 1)
{
x[i] = x[i + 1];
i++;
}
Array.Resize(ref x, i);
break;
}
}
int len = x.Length;
if (len == 2)
{
source = x[0];
severity = (Severe)Enum.Parse(typeof(Severe),x[1].Trim());
message = " ";
AssignData(dateTime, source, severity, message);
}
else
{
if (len >= 4)
{
x[2] = x[2] + x[3];
Array.Resize(ref x, 3);
}
source = x[0];
severity = (Severe)Enum.Parse(typeof(Severe),x[1].Trim());
message = x[2].Trim();
AssignData(dateTime,source,severity,message);
}
}
}
}
}
/// <summary>
/// It is used to add the data into an ObservableCollection named as 'LogViewerModelCollection'.
/// </summary>
/// <param name="dateTime">DateTime for log entry</param>
/// <param name="source">Source File for log entry </param>
/// <param name="severity">Severity for log entry</param>
/// <param name="message">Description for log entry</param>
public void AssignData(DateTime dateTime,string source,Severe severity,string message)
{
_logViewerModelCollection.Add(new LogViewerModel
{
Datetime = dateTime,
Source = source,
Severity = severity,
Message = message
});
}
#region Properties
bool _norm=true;
bool _dbg = true;
bool _crtcl=true;
string _searchSource;
string _startDay;
string _startMonth;
string _startYear;
string _startHour;
string _startMinute;
string _endDay;
string _endMonth;
string _endYear;
string _endHour;
string _endMinute;
public bool LowChk
{
get { return _norm; }
set
{
_norm = value;
OnPropertyChanged("LowChk");
}
}
public bool MediumChk
{
get { return _dbg; }
set
{
_dbg = value;
OnPropertyChanged("MediumChk");
}
}
public bool HighChk
{
get { return _crtcl; }
set
{
_crtcl = value;
OnPropertyChanged("HighChk");
}
}
public string SearchSource
{
get { return _searchSource; }
set
{
_searchSource = value;
OnPropertyChanged("SearchSource");
}
}
public string StartDay
{
get { return _startDay; }
set { _startDay=value;}
}
public string StartMonth
{
get { return _startMonth; }
set { _startMonth = value; }
}
public string StartYear
{
get { return _startYear; }
set { _startYear=value;}
}
public string StartHour
{
get { return _startHour; }
set { _startHour=value;}
}
public string StartMinute
{
get { return _startMinute; }
set { _startMinute = value; }
}
public string EndDay
{
get { return _endDay; }
set { _endDay = value; }
}
public string EndMonth
{
get { return _endMonth; }
set { _endMonth = value; }
}
public string EndYear
{
get { return _endYear; }
set { _endYear=value;}
}
public string EndHour
{
get { return _endHour; }
set { _endHour=value;}
}
public string EndMinute
{
get { return _endMinute; }
set { _endMinute = value; }
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}
RelayCommand's Logic :
namespace LogViewerApplication
{
public class RelayCommand:ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
//public event EventHandler CanExecuteChanged;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
}
APP.xaml CODE:-
namespace LogViewerApplication
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//base.OnStartup(e);
FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentCulture.IetfLanguageTag)));
LogViewerViewModel logViewerViewModel = new LogViewerViewModel();
LogViewer window = new LogViewer();
window.Show();
window.DataContext = logViewerViewModel;
}
}
}
XAML CODE :
<Window.Resources>
<!--<ph:LogViewerViewModel x:Key="log"/>-->
<!--<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Background="LightBlue"
BorderBrush="Black"
BorderThickness="1,1,1,1"
CornerRadius="6,6,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="LightBlue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="Border" Property="Background" Value="LightGray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
<!--<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabPanel
Grid.Row="0"
Panel.ZIndex="1"
Margin="0,0,4,-1"
IsItemsHost="True"
Background="Transparent" />
<Border
Grid.Row="1"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="0, 12, 12, 12" >
<Border.Background>
<LinearGradientBrush>
<GradientStop Color="LightBlue" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<ContentPresenter ContentSource="SelectedContent" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>-->
<Style x:Key="ItemContStyle" TargetType="{x:Type ListViewItem}">
<Style.Resources>
<SolidColorBrush x:Key="TruBrush" Color="Green"/>
<SolidColorBrush x:Key="VinuBrush" Color="Red"/>
</Style.Resources>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Style.Triggers>
<DataTrigger Binding="{Binding Severity}" Value="Debug">
<Setter Property="Background" Value="{StaticResource TruBrush}" />
</DataTrigger>
<DataTrigger Binding="{Binding Severity}" Value="Critical">
<Setter Property="Background" Value="{StaticResource VinuBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.4*" />
<RowDefinition Height="0.6*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" Grid.Column="0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<ListView x:Name="_listView" x:FieldModifier="private" Grid.Row="0" Width="Auto" Height="Auto" Margin="8,9,11,0" ItemContainerStyle="{StaticResource ItemContStyle}" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding ViewSource}" >
<ListView.View>
<GridView x:Name="_gridView">
<GridViewColumn Width="150" DisplayMemberBinding="{Binding Datetime}" Header="Date/Time"/>
<GridViewColumn Width="220" DisplayMemberBinding="{Binding Source}" Header="Source"/>
<GridViewColumn Width="120" DisplayMemberBinding="{Binding Severity}" Header="Severity"/>
<GridViewColumn Width="Auto" DisplayMemberBinding="{Binding Message}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Message" HorizontalContentAlignment="Left"/>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
<TabControl Grid.Row="1" VerticalAlignment="Stretch" Width="Auto" Margin="8,9,11,0">
<TabItem Header="FilterBySeverity">
<StackPanel>
<Expander Header="Color Code" Margin="0,10,0,20" Width="100" HorizontalAlignment="Left" Expanded="Expander_Expanded">
<Expander.ToolTip>
<ToolTip>View Severity in Colors</ToolTip>
</Expander.ToolTip>
<StackPanel>
<RadioButton Margin="20,8,0,8">Enable</RadioButton>
<RadioButton Margin="20,0,0,8">Disable</RadioButton>
</StackPanel>
</Expander>
<CheckBox x:Name="_low" x:FieldModifier="private" Margin="8,10,0,12" IsChecked="{Binding LowChk, UpdateSourceTrigger=PropertyChanged}">Low</CheckBox>
<CheckBox x:Name="_medium" x:FieldModifier="private" Margin="8,0,0,12" IsChecked="{Binding MediumChk, UpdateSourceTrigger=PropertyChanged}">Medium</CheckBox>
<CheckBox x:Name="_high" x:FieldModifier="private" Margin="8,0,0,40" IsChecked="{Binding HighChk, UpdateSourceTrigger=PropertyChanged}">High</CheckBox>
<Button x:Name="_show" Width="100" Margin="8,0,0,0" HorizontalAlignment="Left" Command="{Binding Path=SetColorCommand}" >Show
<Button.BitmapEffect>
<BitmapEffectGroup>
<OuterGlowBitmapEffect/>
<DropShadowBitmapEffect/>
<BevelBitmapEffect/>
</BitmapEffectGroup>
</Button.BitmapEffect>
</Button>
</StackPanel>
</TabItem>
<TabItem Header="FilterByTimeDate">
<Grid Height="250" Width="600" Margin="0,20,350,100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" FontSize="20" FontWeight="Bold" Height="80" Width="Auto">Enter the time range for your search</Label>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Width="50" Margin="0,30,5,0" FontSize="15" FontWeight="Bold">From:</Label>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Height="40" Width="Auto">
<Label>Day:</Label>
<TextBox Margin="0,0,5,5" Height="20" Width="30" Text="{Binding Path=StartDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Month:</Label>
<TextBox Margin="0,0,5,5" Height="20" Width="30" Text="{Binding Path=StartMonth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Year:</Label>
<TextBox Margin="0,0,0,5" Height="20" Width="80" Text="{Binding Path=StartYear, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Height="30" Width="Auto">
<Label>Hour:</Label>
<TextBox Margin="0,0,5,5" Height="20" Width="30" Text="{Binding Path=StartHour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Minute:</Label>
<TextBox Margin="0,2,5,10" Height="20" Width="30" Text="{Binding Path=StartMinute, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<Button Grid.Row="2" Height="30" Width="100" Command="{Binding DateFilterCommand}">Show</Button>
</Grid>
<Label Grid.Column="2" Width="30" Margin="15,30,2,0" FontSize="15" FontWeight="Bold">To:</Label>
<Grid Grid.Column="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Height="40">
<Label>Day:</Label>
<TextBox Margin="0,7,10,15" Height="20" Width="30" Text="{Binding Path=EndDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Month:</Label>
<TextBox Margin="0,7,10,15" Height="20" Width="30" Text="{Binding Path=EndMonth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Year:</Label>
<TextBox Margin="0,7,10,15" Height="20" Width="80" Text="{Binding Path=EndYear, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Height="30">
<Label>Hour:</Label>
<TextBox Margin="0,2,10,10" Height="20" Width="30" Text="{Binding Path=EndHour, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Label>Minute:</Label>
<TextBox Margin="0,2,10,10" Height="20" Width="30" Text="{Binding Path=EndMinute, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
</TabItem>
<TabItem Header="Searching">
<Grid Height="150" Width="300">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Margin="0,12,0,0" Width="82.033" Background="AliceBlue">FindWhat:</Label>
<Label Grid.Row="1" HorizontalAlignment="Right" Margin="0,14,0,0" Width="80.097">EnterText:</Label>
<ComboBox x:Name="FindWhat" x:FieldModifier="private" Grid.Row="0" Grid.Column="1" Margin="0,0,0,13" Text="{Binding Path=SearchSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Content="Source"/>
<ComboBoxItem Content="Message"/>
</ComboBox>
<TextBox x:Name="EnterText" x:FieldModifier="private" Grid.Column="1" Grid.Row="1" Margin="0,14,0,0"/>
<Button Grid.Column="1" Grid.Row="2" Width="75" HorizontalAlignment="Left" Name="CoolTabButton" Margin="0,15,0,12" Command="{Binding SearchCommand}" CommandParameter="{Binding ElementName=EnterText, Path=Text}">OK</Button>
<!--Text="{Binding SearchingText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"-->
</Grid>
</TabItem>
</TabControl>
</Grid>
So guys i am facing a problem, when i select a filter after another filter then i do not get the previous filtered data in my listview..i mean 2nd filter applies on a fresh list....All the three filter are working nicely individually but when work in chaining then always all the filters works on fresh list nt the previous filtered list...
Guys please help me....
please send me a proper code of your logic to solve this problem....
Thanks
You may check if this could suit your needs : http://dotnetexplorer.blog.com/2011/04/07/wpf-itemscontrol-generic-staticreal-time-filter-custom-control-presentation/
This is a multi filter componenent which work on most common .net collection and attach itself to any WPF ItemsControl
The only drawback is that this is a component that stand outside your ItemsControl...

Resources