Squashed 'FSI.Lib/' content from commit 6aa4846

git-subtree-dir: FSI.Lib
git-subtree-split: 6aa48465a834a7bfdd9cbeae8d2e4f769d0c0ff8
This commit is contained in:
maier_S
2022-03-23 10:15:26 +01:00
commit a0095a0516
62 changed files with 8405 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
<UserControl x:Class="FSI.Lib.Wpf.Ctrls.ChbWindowsTopMost"
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"
xmlns:local="clr-namespace:FSI.Lib.Wpf.Ctrls"
mc:Ignorable="d"
Width="Auto"
Height="Auto">
<StackPanel>
<CheckBox Name="chbTopMost"
Content="Fenster immer im Vordergrund"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,89 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
namespace FSI.Lib.Wpf.Ctrls
{
/// <summary>
/// Interaktionslogik für ChbWindowsTopMost.xaml
/// </summary>
public partial class ChbWindowsTopMost : UserControl
{
private Window _window;
//A window receives this message when the user chooses a command from the Window menu, or when the user chooses the maximize button, minimize button, restore button, or close button.
public const Int32 WM_SYSCOMMAND = 0x112;
//Draws a horizontal dividing line.This flag is used only in a drop-down menu, submenu, or shortcut menu.The line cannot be grayed, disabled, or highlighted.
public const Int32 MF_SEPARATOR = 0x800;
//Specifies that an ID is a position index into the menu and not a command ID.
public const Int32 MF_BYPOSITION = 0x400;
//Specifies that the menu item is a text string.
public const Int32 MF_STRING = 0x0;
//Menu Ids for our custom menu items
public const Int32 _ItemTopMostId = 1000;
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, string lpNewItem);
public ChbWindowsTopMost()
{
InitializeComponent();
Loaded += ChbWindowsTopMost_Loaded;
}
private void ChbWindowsTopMost_Loaded(object sender, RoutedEventArgs e)
{
_window = Window.GetWindow(this);
IntPtr windowhandle = new WindowInteropHelper(_window).Handle;
HwndSource hwndSource = HwndSource.FromHwnd(windowhandle);
//Get the handle for the system menu
IntPtr systemMenuHandle = GetSystemMenu(windowhandle, false);
//Insert our custom menu items
InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); //Add a menu seperator
InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _ItemTopMostId, "immer im Vordergrund"); //Add a setting menu item
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Check if the SystemCommand message has been executed
if (msg == WM_SYSCOMMAND)
{
//check which menu item was clicked
switch (wParam.ToInt32())
{
case _ItemTopMostId:
_window.Topmost = !_window.Topmost;
chbTopMost.IsChecked = _window.Topmost;
handled = true;
break;
}
}
return IntPtr.Zero;
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
_window.Topmost = true;
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
_window.Topmost = false;
}
}
}

View File

@@ -0,0 +1,78 @@
using System.Windows;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public sealed class DataGridTemplateColumn : System.Windows.Controls.DataGridTemplateColumn
{
#region Public Fields
/// <summary>
/// FieldName Dependency Property.
/// </summary>
public static readonly DependencyProperty FieldNameProperty =
DependencyProperty.Register("FieldName", typeof(string), typeof(DataGridTemplateColumn),
new PropertyMetadata(""));
/// <summary>
/// IsColumnFiltered Dependency Property.
/// </summary>
public static readonly DependencyProperty IsColumnFilteredProperty =
DependencyProperty.Register("IsColumnFiltered", typeof(bool), typeof(DataGridTemplateColumn),
new PropertyMetadata(false));
#endregion Public Fields
#region Public Properties
public string FieldName
{
get => (string)GetValue(FieldNameProperty);
set => SetValue(FieldNameProperty, value);
}
public bool IsColumnFiltered
{
get => (bool)GetValue(IsColumnFilteredProperty);
set => SetValue(IsColumnFilteredProperty, value);
}
#endregion Public Properties
}
public sealed class DataGridTextColumn : System.Windows.Controls.DataGridTextColumn
{
#region Public Fields
/// <summary>
/// FieldName Dependency Property.
/// </summary>
public static readonly DependencyProperty FieldNameProperty =
DependencyProperty.Register("FieldName", typeof(string), typeof(DataGridTextColumn),
new PropertyMetadata(""));
/// <summary>
/// IsColumnFiltered Dependency Property.
/// </summary>
public static readonly DependencyProperty IsColumnFilteredProperty =
DependencyProperty.Register("IsColumnFiltered", typeof(bool), typeof(DataGridTextColumn),
new PropertyMetadata(false));
#endregion Public Fields
#region Public Properties
public string FieldName
{
get => (string)GetValue(FieldNameProperty);
set => SetValue(FieldNameProperty, value);
}
public bool IsColumnFiltered
{
get => (bool)GetValue(IsColumnFilteredProperty);
set => SetValue(IsColumnFilteredProperty, value);
}
#endregion Public Properties
}
}

View File

@@ -0,0 +1,319 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public sealed class FilterCommon : NotifyProperty
{
#region Public Constructors
public FilterCommon()
{
PreviouslyFilteredItems = new HashSet<object>(EqualityComparer<object>.Default);
}
#endregion Public Constructors
#region Public Properties
public string FieldName { get; set; }
public Type FieldType { get; set; }
public bool IsFiltered { get; set; }
public HashSet<object> PreviouslyFilteredItems { get; set; }
// Treeview
public List<FilterItem> Tree { get; set; }
public Loc Translate { get; set; }
#endregion Public Properties
#region Private Methods
/// <summary>
/// Recursive call for check/uncheck all items in tree
/// </summary>
/// <param name="state"></param>
/// <param name="updateChildren"></param>
/// <param name="updateParent"></param>
private void SetIsChecked(FilterItem item, bool? state, bool updateChildren, bool updateParent)
{
try
{
if (state == item.IsChecked) return;
item.SetState = state;
// select all / unselect all
if (item.Level == 0)
Tree.Where(t => t.Level != 0).ToList().ForEach(c => { SetIsChecked(c, state, true, true); });
// update children
if (updateChildren && item.IsChecked.HasValue)
item.Children?.ForEach(c => { SetIsChecked(c, state, true, false); });
// update parent
if (updateParent && item.Parent != null)
VerifyCheckedState(item.Parent);
}
catch (Exception ex)
{
Debug.WriteLine($"FilterCommon.SetState : {ex.Message}");
throw;
}
}
/// <summary>
/// Update the tree when the state of the IsChecked property changes
/// </summary>
/// <param name="o">item</param>
/// <param name="e">state</param>
public void UpdateTree(object o, bool? e)
{
if (o == null) return;
var item = (FilterItem)o;
SetIsChecked(item, e, true, true);
}
/// <summary>
/// Check or uncheck parents or children
/// </summary>
private void VerifyCheckedState(FilterItem item)
{
bool? state = null;
for (var i = 0; i < item.Children?.Count; ++i)
{
var current = item.Children[i].IsChecked;
if (i == 0)
{
state = current;
}
else if (state != current)
{
state = null;
break;
}
}
SetIsChecked(item, state, false, true);
}
#endregion Private Methods
#region Public Methods
/// <summary>
/// Add the filter to the predicate dictionary
/// </summary>
public void AddFilter(Dictionary<string, Predicate<object>> criteria)
{
if (IsFiltered) return;
// predicate of filter
bool Predicate(object o)
{
var value = o.GetType().GetProperty(FieldName)?.GetValue(o, null);
return !PreviouslyFilteredItems.Contains(value);
}
// add to list of predicates
criteria.Add(FieldName, Predicate);
IsFiltered = true;
}
/// <summary>
/// Any Date IsChecked, check if any tree item is checked (can apply filter)
/// </summary>
/// <returns></returns>
public bool AnyDateIsChecked()
{
// any IsChecked is true or null
// IsDate Checked has three states, isChecked: null and true
return Tree != null && Tree.Skip(1).Any(t => t.IsChecked != false);
}
/// <summary>
/// Any state of Date Changed, check if at least one date is checked and another is changed
/// </summary>
/// <returns></returns>
public bool AnyDateChanged()
{
// any (year, month, day) status changed
return Tree != null &&
Tree.Skip(1)
.Any(year => year.Changed || year.Children
.Any(month => month.Changed || month.Children
.Any(day => day.Changed))) && AnyDateIsChecked();
}
/// <summary>
/// Build the item tree
/// </summary>
/// <param name="dates"></param>
/// <param name="currentFilter"></param>
/// <param name="uncheckPrevious"></param>
/// <returns></returns>
public IEnumerable<FilterItem> BuildTree(IEnumerable<object> dates, string lastFilter = null)
{
try
{
var uncheckPrevious = FieldName == lastFilter;
var type = typeof(DateTime);
Tree = new List<FilterItem>
{
new FilterItem(this)
{
Label = Translate.All, CurrentFilter = this, Content = 0, Level = 0, SetState = true, FieldType = type
}
};
if (dates == null) return Tree;
// iterate over all items that are not null
// INFO:
// SetState : does not raise OnDateStatusChanged event
// IsChecked : raise OnDateStatusChanged event
// (see the FilterItem class for more informations)
var dateTimes = dates.ToList();
foreach (var y in from date in dateTimes.Where(d => d != null)
.Select(d => (DateTime)d).OrderBy(o => o.Year)
group date by date.Year into year
select new FilterItem(this)
{
// YEAR
Level = 1,
CurrentFilter = this,
Content = year.Key,
Label = year.First().ToString("yyyy", Translate.Culture),
SetState = true, // default state
FieldType = type,
Children = (from date in year
group date by date.Month into month
select new FilterItem(this)
{
// MOUNTH
Level = 2,
CurrentFilter = this,
Content = month.Key,
Label = month.First().ToString("MMMM", Translate.Culture),
SetState = true, // default state
FieldType = type,
Children = (from day in month
select new FilterItem(this)
{
// DAY
Level = 3,
CurrentFilter = this,
Content = day.Day,
Label = day.ToString("dd", Translate.Culture),
SetState = true, // default state
FieldType = type,
Children = new List<FilterItem>()
}).ToList()
}).ToList()
})
{
// set parent and IsChecked property if uncheckPrevious items
y.Children.ForEach(m =>
{
m.Parent = y;
m.Children.ForEach(d =>
{
d.Parent = m;
// set the state of the ischecked property based on the items already filtered (unchecked)
if (PreviouslyFilteredItems != null && uncheckPrevious)
d.IsChecked = PreviouslyFilteredItems
.Any(u => u != null && u.Equals(new DateTime((int)y.Content, (int)m.Content, (int)d.Content))) == false;
// reset initialization with new state
d.InitialState = d.IsChecked;
});
// reset initialization with new state
m.InitialState = m.IsChecked;
});
// reset initialization with new state
y.InitialState = y.IsChecked;
Tree.Add(y);
}
// last empty item if exist in collection
if (dateTimes.Any(d => d == null))
Tree.Add(
new FilterItem(this)
{
Label = Translate.Empty, // translation
CurrentFilter = this,
Content = null,
Level = -1,
FieldType = type,
SetState = PreviouslyFilteredItems?.Any(u => u == null) == false,
Children = new List<FilterItem>()
}
);
}
catch (Exception ex)
{
Debug.WriteLine($"FilterCommon.BuildTree : {ex.Message}");
throw;
}
return Tree;
}
/// <summary>
/// Get all the items from the tree (checked or unchecked)
/// </summary>
/// <returns></returns>
public IEnumerable<FilterItem> GetAllItemsTree()
{
var filterCommon = new List<FilterItem>();
try
{
// skip first item (select all)
foreach (var y in Tree.Skip(1))
if (y.Level > 0) // year :1, mounth : 2, day : 3
filterCommon.AddRange(
from m in y.Children
from d in m.Children
select new FilterItem
{
Content = new DateTime((int)y.Content, (int)m.Content, (int)d.Content),
IsChecked = d.IsChecked ?? false,
});
else // null date (Level -1)
filterCommon.Add(new FilterItem
{
Content = null,
IsChecked = y.IsChecked ?? false,
});
}
catch (Exception ex)
{
Debug.WriteLine($"FilterCommon.GetAllItemsTree : {ex.Message}");
throw;
}
return filterCommon;
}
#endregion Public Methods
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
/// <summary>
/// ResourceDictionary
/// </summary>
public partial class FilterDataGridDictionary
{
#region Public Constructors
/// <summary>
/// FilterDataGrid Dictionary
/// </summary>
public FilterDataGridDictionary()
{
InitializeComponent();
}
#endregion Public Constructors
}
}

View File

@@ -0,0 +1,396 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public static class Extensions
{
#region Public Methods
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
#endregion Public Methods
}
public static class Helpers
{
#region Public Methods
/// <summary>
/// Print elapsed time
/// </summary>
/// <param name="label"></param>
/// <param name="start"></param>
public static void Elapsed(string label, DateTime start)
{
var span = DateTime.Now - start;
Debug.WriteLine($"{label,-20}{span:mm\\:ss\\.ff}");
}
#endregion Public Methods
}
public static class VisualTreeHelpers
{
#region Private Methods
private static T FindVisualChild<T>(this DependencyObject dependencyObject, string name)
where T : DependencyObject
{
// Search immediate children first (breadth-first)
var childrenCount = VisualTreeHelper.GetChildrenCount(dependencyObject);
//http://stackoverflow.com/questions/12304904/why-visualtreehelper-getchildrencount-returns-0-for-popup
if (childrenCount == 0 && dependencyObject is Popup)
{
var popup = dependencyObject as Popup;
return popup.Child?.FindVisualChild<T>(name);
}
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
var nameOfChild = child.GetValue(FrameworkElement.NameProperty) as string;
if (child is T && (name == string.Empty || name == nameOfChild))
return (T)child;
var childOfChild = child.FindVisualChild<T>(name);
if (childOfChild != null)
return childOfChild;
}
return null;
}
private static IEnumerable<T> GetChildrenOf<T>(this DependencyObject obj, bool recursive) where T : DependencyObject
{
var count = VisualTreeHelper.GetChildrenCount(obj);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is T) yield return (T)child;
if (recursive)
foreach (var item in child.GetChildrenOf<T>())
yield return item;
}
}
private static IEnumerable<T> GetChildrenOf<T>(this DependencyObject obj) where T : DependencyObject
{
return obj.GetChildrenOf<T>(false);
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent" /> method, which also
/// supports content elements. Keep in mind that for content element,
/// this method falls back to the logical tree of the element!
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>
/// The submitted item's parent, if available. Otherwise
/// null.
/// </returns>
private static DependencyObject GetParentObject(this DependencyObject child)
{
if (child == null) return null;
//handle content elements separately
var contentElement = child as ContentElement;
if (contentElement != null)
{
var parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
var fce = contentElement as FrameworkContentElement;
return fce?.Parent;
}
//also try searching for parent in framework elements (such as DockPanel, etc)
var frameworkElement = child as FrameworkElement;
if (frameworkElement != null)
{
var parent = frameworkElement.Parent;
if (parent != null) return parent;
}
//if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion Private Methods
#region Public Methods
/// <summary>
/// Returns the first ancester of specified type
/// </summary>
public static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T) return (T)current;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
/// <summary>
/// Returns a specific ancester of an object
/// </summary>
public static T FindAncestor<T>(DependencyObject current, T lookupItem)
where T : DependencyObject
{
while (current != null)
{
if (current is T && current == lookupItem) return (T)current;
current = VisualTreeHelper.GetParent(current);
}
return null;
}
/// <summary>
/// Finds an ancestor object by name and type
/// </summary>
public static T FindAncestor<T>(DependencyObject current, string parentName)
where T : DependencyObject
{
while (current != null)
{
if (!string.IsNullOrEmpty(parentName))
{
var frameworkElement = current as FrameworkElement;
if (current is T && frameworkElement != null && frameworkElement.Name == parentName)
return (T)current;
}
else if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
}
return null;
}
/// <summary>
/// Looks for a child control within a parent by name
/// </summary>
public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
var childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
/// <summary>
/// Looks for a child control within a parent by type
/// </summary>
public static T FindChild<T>(DependencyObject parent)
where T : DependencyObject
{
// Confirm parent is valid.
if (parent == null) return null;
T foundChild = null;
var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
var childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
public static T FindVisualChild<T>(this DependencyObject dependencyObject) where T : DependencyObject
{
return dependencyObject.FindVisualChild<T>(string.Empty);
}
public static Visual GetDescendantByType(Visual element, Type type)
{
if (element == null) return null;
if (element.GetType() == type) return element;
Visual foundElement = null;
if (element is FrameworkElement frameworkElement) frameworkElement.ApplyTemplate();
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = GetDescendantByType(visual, type);
if (foundElement != null) break;
}
return foundElement;
}
public static DataGridColumnHeader GetHeader(DataGridColumn column, DependencyObject reference)
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child is DataGridColumnHeader colHeader && colHeader.Column == column) return colHeader;
colHeader = GetHeader(column, child);
if (colHeader != null) return colHeader;
}
return null;
}
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">
/// A direct or indirect child of the
/// queried item.
/// </param>
/// <returns>
/// The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.
/// </returns>
public static T TryFindParent<T>(this DependencyObject child) where T : DependencyObject
{
//get parent item
var parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
var parent = parentObject as T;
if (parent != null)
return parent;
return TryFindParent<T>(parentObject);
}
#endregion Public Methods
}
/// <summary>
/// Base class for all ViewModel classes in the application. Provides support for
/// property changes notification.
/// </summary>
[Serializable]
public abstract class NotifyProperty : INotifyPropertyChanged
{
#region Public Events
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion Public Events
#region Private Methods
/// <summary>
/// Warns the developer if this object does not have a public property with
/// the specified name. This method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
private void VerifyPropertyName(string propertyName)
{
// verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
Debug.Fail("Invalid property name: " + propertyName);
}
#endregion Private Methods
#region Public Methods
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the property that has a new value.</param>
public void OnPropertyChanged(string propertyName)
{
VerifyPropertyName(propertyName);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion Public Methods
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public class FilterItem : NotifyProperty
{
#region Public Events
private event EventHandler<bool?> OnDateStatusChanged;
#endregion Public Events
#region Constructor
public FilterItem(FilterCommon action = null)
{
// event subscription
if (action != null)
OnDateStatusChanged += action.UpdateTree;
}
#endregion Constructor
#region Private Fields
private bool? isChecked;
private bool initialized;
#endregion Private Fields
#region Public Properties
/// <summary>
///Children higher levels (years, months)
/// </summary>
public List<FilterItem> Children { get; set; }
/// <summary>
/// Raw value of the item (not displayed, see Label property)
/// </summary>
public object Content { get; set; }
/// <summary>
/// Content length
/// </summary>
public int ContentLength { get; set; }
/// <summary>
/// Current filter
/// </summary>
public FilterCommon CurrentFilter { get; set; }
/// <summary>
/// Field type
/// </summary>
public Type FieldType { get; set; }
/// <summary>
/// Initial state
/// </summary>
public bool? InitialState { get; set; }
public int Id { get; set; }
/// <summary>
/// State of checkbox
/// </summary>
public bool? IsChecked
{
get => isChecked;
set
{
if (!initialized)
{
InitialState = value;
initialized = true;
isChecked = value; // don't remove
// the iteration over an Collection triggers the notification
// of the "IsChecked" property and slows the performance of the loop,
// the return prevents the OnPropertyChanged
// notification at initialization
return;
}
// raise event to update the date tree, see FilterCommon class
// only type date type fields are subscribed to the OnDateStatusChanged event
// OnDateStatusChanged is not triggered at tree initialization
if (FieldType == typeof(DateTime))
{
OnDateStatusChanged?.Invoke(this, value);
}
else
{
isChecked = value;
OnPropertyChanged("IsChecked");
}
}
}
/// <summary>
/// Content displayed
/// </summary>
public string Label { get; set; }
/// <summary>
/// Hierarchical level for the date
/// </summary>
public int Level { get; set; }
/// <summary>
/// Parent of lower levels (days, months)
/// </summary>
public FilterItem Parent { get; set; }
/// <summary>
/// Set the state of the IsChecked property for date, does not invoke the update of the tree
/// </summary>
public bool? SetState
{
get => isChecked;
set
{
isChecked = value;
if (!initialized)
{
InitialState = value;
initialized = true;
}
else
{
OnPropertyChanged("IsChecked");
}
}
}
/// <summary>
/// Checks if the initial state has changed
/// </summary>
public bool Changed => isChecked != InitialState;
#endregion Public Properties
}
}

View File

@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public enum Local
{
Chinese,
Dutch,
English,
French,
German,
Italian,
Russian,
}
public class Loc
{
#region Private Fields
private Local language;
// culture name(used for dates)
private static readonly Dictionary<Local, string> CultureNames = new Dictionary<Local, string>
{
{ Local.Chinese, "zh-Hans" },
{ Local.Dutch, "nl-NL" },
{ Local.English, "en-US" },
{ Local.French, "fr-FR" },
{ Local.German, "de-DE" },
{ Local.Italian, "it-IT" },
{ Local.Russian, "ru-RU" },
};
/// <summary>
/// Translation dictionary
/// </summary>
private static readonly Dictionary<string, Dictionary<Local, string>> Translation =
new Dictionary<string, Dictionary<Local, string>>
{
{
"All", new Dictionary<Local, string>
{
{ Local.Chinese, "(全选)" },
{ Local.Dutch, "(Alles selecteren)" },
{ Local.English, "(Select all)" },
{ Local.French, "(Sélectionner tout)" },
{ Local.German, "(Alle auswählen)" },
{ Local.Italian, "(Seleziona tutto)" },
{ Local.Russian, "(Выбрать все)" },
}
},
{
"Empty", new Dictionary<Local, string>
{
{ Local.Chinese, "(空白)" },
{ Local.Dutch, "(Leeg)" },
{ Local.English, "(Blank)" },
{ Local.French, "(Vides)" },
{ Local.German, "(Leer)" },
{ Local.Italian, "(Vuoto)" },
{ Local.Russian, "(Заготовки)" },
}
},
{
"Clear", new Dictionary<Local, string>
{
{ Local.Chinese, "清除过滤器 \"{0}\"" },
{ Local.Dutch, "Filter \"{0}\" verwijderen" },
{ Local.English, "Clear filter \"{0}\"" },
{ Local.French, "Effacer le filtre \"{0}\"" },
{ Local.German, "Filter löschen \"{0}\"" },
{ Local.Italian, "Cancella filtro \"{0}\"" },
{ Local.Russian, "Очистить фильтр \"{0}\"" },
}
},
{
"Contains", new Dictionary<Local, string>
{
{ Local.Chinese, "搜索(包含)" },
{ Local.Dutch, "Zoek (bevat)" },
{ Local.English, "Search (contains)" },
{ Local.French, "Rechercher (contient)" },
{ Local.German, "Suche (enthält)" },
{ Local.Italian, "Cerca (contiene)" },
{ Local.Russian, "Искать (содержит)" },
}
},
{
"StartsWith", new Dictionary<Local, string>
{
{ Local.Chinese, "搜索 (来自)" },
{ Local.Dutch, "Zoek (beginnen met)" },
{ Local.English, "Search (startswith)" },
{ Local.French, "Rechercher (commence par)" },
{ Local.German, "Suche (beginnen mit)" },
{ Local.Italian, "Cerca (inizia con)" },
{ Local.Russian, "Искать (hачни с)" },
}
},
{
"Toggle", new Dictionary<Local, string>
{
{ Local.Chinese, "切換包含/開始於" },
{ Local.Dutch, "Toggle bevat/begint met" },
{ Local.English, "Toggle contains/startswith" },
{ Local.French, "Basculer contient/commence par" },
{ Local.German, "Toggle enthält/beginnt mit" },
{ Local.Italian, "Toggle contiene/inizia con" },
{ Local.Russian, "Переключить содержит/начинается с" },
}
},
{
"Ok", new Dictionary<Local, string>
{
{ Local.Chinese, "确定" },
{ Local.Dutch, "Ok" },
{ Local.English, "Ok" },
{ Local.French, "Ok" },
{ Local.German, "Ok" },
{ Local.Italian, "Ok" },
{ Local.Russian, "Ok" },
}
},
{
"Cancel", new Dictionary<Local, string>
{
{ Local.Chinese, "取消" },
{ Local.Dutch, "Annuleren" },
{ Local.English, "Cancel" },
{ Local.French, "Annuler" },
{ Local.German, "Abbrechen" },
{ Local.Italian, "Annulla" },
{ Local.Russian, "Отмена" },
}
},
{
"Status", new Dictionary<Local, string>
{
{ Local.Chinese, "{0:n0} 找到了 {1:n0} 条记录" },
{ Local.Dutch, "{0:n0} rij(en) gevonden op {1:n0}" },
{ Local.English, "{0:n0} record(s) found on {1:n0}" },
{ Local.French, "{0:n0} enregistrement(s) trouvé(s) sur {1:n0}" },
{ Local.German, "{0:n0} zeilen angezeigt von {1:n0}" },
{ Local.Italian, "{0:n0} oggetti trovati su {1:n0}" },
{ Local.Russian, "{0:n0} записей найдено на {1:n0}" },
}
},
{
"ElapsedTime", new Dictionary<Local, string>
{
{ Local.Chinese, "经过时间{0:mm}:{0:ss}.{0:ff}" },
{ Local.Dutch, "Verstreken tijd {0:mm}:{0:ss}.{0:ff}" },
{ Local.English, "Elapsed time {0:mm}:{0:ss}.{0:ff}" },
{ Local.French, "Temps écoulé {0:mm}:{0:ss}.{0:ff}" },
{ Local.German, "Verstrichene Zeit {0:mm}:{0:ss}.{0:ff}" },
{ Local.Italian, "Tempo trascorso {0:mm}:{0:ss}.{0:ff}" },
{ Local.Russian, "Пройденное время {0:mm}:{0:ss}.{0:ff}" },
}
}
};
#endregion Private Fields
#region Constructors
public Loc()
{
Language = Local.English;
}
#endregion Constructors
#region Public Properties
public Local Language
{
get => language;
set
{
language = value;
Culture = new CultureInfo(CultureNames[value]);
}
}
public CultureInfo Culture { get; private set; }
public string CultureName => CultureNames[Language];
public string LanguageName => Enum.GetName(typeof(Local), Language);
public string All => Translate("All");
public string Cancel => Translate("Cancel");
public string Clear => Translate("Clear");
public string Contains => Translate("Contains");
public string ElapsedTime => Translate("ElapsedTime");
public string Empty => Translate("Empty");
public string Ok => Translate("Ok");
public string StartsWith => Translate("StartsWith");
public string Status => Translate("Status");
public string Toggle => Translate("Toggle");
#endregion Public Properties
#region Private Methods
/// <summary>
/// Translated into the language
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private string Translate(string key)
{
return Translation.ContainsKey(key) && Translation[key].ContainsKey(Language)
? Translation[key][Language]
: "unknow";
}
#endregion Private Methods
}
}

View File

@@ -0,0 +1 @@
https://github.com/macgile/DataGridFilter

View File

@@ -0,0 +1,36 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace FSI.Lib.Wpf.Ctrls.FilterDataGrid
{
public class StringFormatConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
// values [0] contains the format
if (values[0] == DependencyProperty.UnsetValue || string.IsNullOrEmpty(values[0]?.ToString()))
return string.Empty;
var stringFormat = values[0].ToString();
return string.Format(stringFormat, values.Skip(1).ToArray());
}
catch (FormatException ex)
{
Debug.WriteLine($"StringFormatConverter.Convert error: {ex.Message}");
return string.Empty;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,880 @@
<ResourceDictionary x:Class="FSI.Lib.Wpf.Ctrls.FilterDataGrid.FilterDataGridDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- STRING FORMAT CONVERTER -->
<control:StringFormatConverter x:Key="StringFormatConverter" />
<!-- INITIAL POPUP SIZE -->
<sys:Double x:Key="PopupHeight">420</sys:Double>
<sys:Double x:Key="PopupWidth">262</sys:Double>
<sys:Boolean x:Key="StayOpen">False</sys:Boolean>
<!-- https://yqnn.github.io/svg-path-editor/ -->
<!-- FILTER SET ICON -->
<Geometry x:Key="FilterSet">
M 0 17 H 12 L 6 25 Z M 6 0 H 29 L 29 3 L 20 10 L 20 21 H 15 L 15 10 L 6 3 Z
</Geometry>
<!-- FILTER BUTTON ICON -->
<Geometry x:Key="Filter">
M 7 10 L 12 15 L 17 10 H 7 Z
</Geometry>
<!-- DELETE FILTER ICON -->
<Geometry x:Key="FilterDelete">
M11.1 11.4L8.5 8.9L9.8 7.6L12.3 10.1L14.8 7.6L16.1 8.9L13.6 11.4L16.1 13.9L14.8 15.2L12.3 12.6L9.8 15.2L8.5 13.9ZM0 0H13L13 2L8 6V14L5 11V6L0 2Z
</Geometry>
<!-- BOX CHECKED ICON -->
<Geometry x:Key="FilterChecked">
M 125 125 L 0 125 V 0 H 125 Z M 1 124 H 124 V 1 H 1 Z M 20 68 L 29 57 L 56 80 L 98 25 L 110 35 L 59 101 Z
</Geometry>
<!-- GRIPSIZE ICON -->
<Geometry x:Key="GripSizeIcon">
M0 9L2 9M4 9L6 9M8 9L10 9M1 8L1 10M5 8L5 10M9 8L9 10M4 5L6 5M8 5L10 5M5 4L5 6M9 4L9 6M8 1L10 1M9 0L9 2
</Geometry>
<!-- SEARCH MAGNIFIER ICON -->
<Geometry x:Key="Magnifier">
M9.6 8.5H9L8.7 8.2C9.6 7.4 10 6.2 10 5C10 2.2 7.8 0 5 0S0 2.2 0 5S2.2 10 5 10C6.2 10 7.4 9.6 8.2 8.7L8.5
9V9.6L12.3 13.5L13.5 12.3L9.6 8.5ZM5 8.5C3.1 8.5 1.5 6.9 1.5 5S3.1 1.5 5 1.5S8.5 3.1 8.5 5S6.9 8.5 5 8.5Z
</Geometry>
<!-- SEARCH DELETE ICON -->
<Geometry x:Key="Delete">
M 0 0 M 2 3 L 3 2 L 8 7 L 13 2 L 14 3 L 9 8 L 14 13 L 13 14 L 8 9 L 3 14 L 2 13 L 7 8 Z M 16 0 M 0 16 M 16 16
</Geometry>
<!-- SEARCH CONTAINS ICON -->
<Geometry x:Key="StartsWith">
M 0.6 1.3 V 0 C 1.1 -0.1 5.5 -1.7 6.2 1.6 V 7.2 H 5 V 6.2 C 4.5 6.7 4 7 3.5 7.2 H 1.4 C 0.4 6.8 0.1 6.1 0 5.2 C 0 4.7 0 3 3 2.8 H 4.9 V 1.7
C 4.4 0.2 2.3 0.6 1 1.1 Z M 4.9 5.1 V 3.8 H 2.6 V 3.8 C 1 3.9 0.9 6.2 2.6 6.2 C 3.5 6.2 4 5.9 4.7 5.3 Z M 9.3 0.9 L 9.5 3.5 L 7.4 2 L 6.9 2.9
L 9.2 4 L 6.9 5.1 L 7.3 6.1 L 9.5 4.7 L 9.3 7.2 H 10.4 L 10.2 4.6 L 12.3 6 L 12.8 5.1 L 10.5 4.1 L 12.8 2.9 L 12.3 2 L 10.2 3.5 L 10.4 0.9 Z
M 16.5 0.9 H 17.6 L 17.4 3.4 L 19.5 2 L 20 2.9 L 17.7 4 L 20 5.2 L 19.5 6 L 17.4 4.7 L 17.6 7.2 H 16.5 L 16.6 4.7 L 14.5 6 L 14 5.2 L 16.3 4
L 14 2.9 L 14.6 2 L 16.7 3.4 Z
</Geometry>
<!-- SEARCH STARTSWITH ICON -->
<Geometry x:Key="Contains">
M 7.2 1.3 V 0 C 7.7 -0.1 12.1 -1.7 12.8 1.6 V 7.2 H 11.6 V 6.2 C 11.1 6.7 10.6 7 10.1 7.2 H 8 C 7 6.8 6.7 6.1 6.6 5.2 C 6.6 4.7 6.6 3 9.6 2.8
H 11.5 V 1.7 C 11 0.2 8.9 0.6 7.6 1.1 Z M 11.5 5.1 V 3.8 H 9.2 C 7.5 4.1 8.1 6.2 9.2 6.2 C 10.1 6.2 10.6 5.9 11.3 5.3 Z M 2.4 1 L 2.6 3.5
L 0.5 2.1 L 0 3 L 2.2 4.1 L 0 5.3 L 0.4 6.1 L 2.6 4.7 L 2.4 7.2 H 3.5 L 3.3 4.7 L 5.4 6.1 L 5.9 5.2 L 3.6 4.1 L 5.9 3 L 5.4 2.1 L 3.3 3.5
L 3.5 1 Z M 16.5 0.9 H 17.6 L 17.4 3.5 L 19.5 2 L 20 3 L 17.7 4 L 20 5.2 L 19.5 6 L 17.4 4.7 L 17.6 7.2 H 16.5 L 16.6 4.7 L 14.5 6 L 14 5.2
L 16.3 4.1 L 14 2.9 L 14.6 2 L 16.6 3.4 Z
</Geometry>
<!-- PLACEHOLDER SEARCH BOX -->
<Style x:Key="PlaceHolder"
TargetType="{x:Type TextBox}">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="SearchControlTemplate"
TargetType="{x:Type TextBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- SEARCH TEXTBOX -->
<TextBox x:Name="TextSource"
Grid.Column="0"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Panel.ZIndex="2"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Focusable="True"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
MaxLength="{TemplateBinding MaxLength}"
Text="{Binding Path=Text, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}" />
<TextBox x:Name="TextBoxPlaceHolder"
Grid.Column="0"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Panel.ZIndex="1"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
MaxLength="{TemplateBinding MaxLength}"
Text="{TemplateBinding Tag}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, ElementName=TextSource}"
Value="">
<Setter Property="Foreground"
Value="LightGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<!-- BUTTON CLEAR FILTER -->
<Button x:Name="ClearSearchBoxBtn"
Grid.Column="1"
Margin="2"
Background="Transparent"
Command="{x:Static control:FilterDataGrid.ClearSearchBox}"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="Transparent"
SnapsToDevicePixels="True">
<!-- MAGNIFIER / DELETE ICON -->
<Path x:Name="PathButton"
Width="18"
Height="18"
Margin="0"
Data="{StaticResource Delete}"
Fill="DarkSlateGray"
SnapsToDevicePixels="True"
Stretch="Uniform"
UseLayoutRounding="True" />
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=TextSource, Path=Text}"
Value="">
<Setter Property="IsEnabled"
Value="False" />
<Setter TargetName="PathButton"
Property="Data"
Value="{StaticResource Magnifier}" />
</DataTrigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Cursor"
Value="Hand" />
<Setter TargetName="PathButton"
Property="Fill"
Value="Red" />
<Setter TargetName="PathButton"
Property="Stroke"
Value="Red" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
<!-- SEPARATOR -->
<Border Grid.Column="2"
Width="1"
Margin="2,0,2,0"
Background="LightGray" />
<!-- TOGGLE BUTTON -->
<ToggleButton Name="SearchToggleButton"
Grid.Column="3"
Margin="2,0,2,0"
VerticalAlignment="Stretch"
Background="Transparent"
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
IsChecked="{Binding StartsWith, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Translate.Toggle}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Padding="3"
Background="Transparent"
BorderBrush="LightGray"
BorderThickness="0">
<Path x:Name="PathToggle"
VerticalAlignment="Center"
Data="{StaticResource Contains}"
Fill="DarkSlateGray"
Stretch="UniformToFill" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter TargetName="PathToggle"
Property="Data"
Value="{StaticResource StartsWith}" />
</Trigger>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="PathToggle"
Property="Fill"
Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
</Grid>
<!-- PLACEHOLDER TEXT -->
<ControlTemplate.Triggers>
<Trigger Property="IsFocused"
Value="True">
<Setter TargetName="TextSource"
Property="FocusManager.FocusedElement"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Trigger>
<DataTrigger Binding="{Binding ElementName=SearchToggleButton, Path=IsChecked}"
Value="True">
<Setter TargetName="TextBoxPlaceHolder"
Property="Text"
Value="{Binding Translate.StartsWith, Mode=OneWay}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SearchToggleButton, Path=IsChecked}"
Value="False">
<Setter TargetName="TextBoxPlaceHolder"
Property="Text"
Value="{Binding Translate.Contains, Mode=OneWay}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- DEFAULT STYLE AND CONTROLTEMPLATE FOR DATAGRID -->
<Style BasedOn="{StaticResource {x:Type DataGrid}}"
TargetType="{x:Type control:FilterDataGrid}">
<!-- DISABLING CanUserAddRows : AggregateException when user can add row -->
<Setter Property="CanUserAddRows"
Value="False" />
<!-- ROWS COUNT TEMPLATE -->
<Setter Property="RowHeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}, Path=Header}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGrid}">
<Border Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- CONTENT DATAGRID -->
<ScrollViewer x:Name="DG_ScrollViewer"
Grid.Row="0"
CanContentScroll="True"
Focusable="false">
<ScrollViewer.Template>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Width="{Binding CellsPanelHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
Command="{x:Static DataGrid.SelectAllCommand}"
Focusable="false"
Style="{DynamicResource {ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle,
TypeInTargetAssembly={x:Type DataGrid}}}"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.All}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<DataGridColumnHeadersPresenter x:Name="PART_ColumnHeadersPresenter"
Grid.Row="0"
Grid.Column="1"
Visibility="{Binding HeadersVisibility, ConverterParameter={x:Static DataGridHeadersVisibility.Column}, Converter={x:Static DataGrid.HeadersVisibilityConverter}, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<ScrollContentPresenter x:Name="PART_ScrollContentPresenter"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
CanContentScroll="{TemplateBinding CanContentScroll}" />
<ScrollBar x:Name="PART_VerticalScrollBar"
Grid.Row="1"
Grid.Column="2"
Maximum="{TemplateBinding ScrollableHeight}"
Orientation="Vertical"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{Binding VerticalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
<Grid Grid.Row="2"
Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding NonFrozenColumnsViewportHorizontalOffset, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ScrollBar x:Name="PART_HorizontalScrollBar"
Grid.Column="1"
Maximum="{TemplateBinding ScrollableWidth}"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{Binding HorizontalOffset, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}" />
</Grid>
</Grid>
</ControlTemplate>
</ScrollViewer.Template>
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
<!-- STATUS BAR & RESULT FILTER -->
<Border x:Name="BorderStatusBar"
Grid.Row="1"
Padding="4,2"
Background="Transparent"
BorderBrush="LightGray"
BorderThickness="0,1">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility"
Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowStatusBar, RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
Value="True">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<UniformGrid Columns="2"
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
<!-- RESULT STATUS -->
<TextBlock HorizontalAlignment="Left">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<Binding Path="Translate.Status" />
<Binding Path="Items.Count"
UpdateSourceTrigger="PropertyChanged" />
<Binding Path="ItemsSourceCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- ELAPSED TIME -->
<TextBlock HorizontalAlignment="Right">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<Binding Path="Translate.ElapsedTime"
UpdateSourceTrigger="PropertyChanged" />
<Binding Path="ElapsedTime" />
</MultiBinding>
</TextBlock.Text>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility"
Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShowElapsedTime}"
Value="True">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</UniformGrid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- DATAGRIDCOLUMNHEADER STYLE -->
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}"
TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
</Style>
<!-- DATATEMPLATE OF DATAGRIDCOLUMNHEADER -->
<DataTemplate x:Key="DataGridHeaderTemplate">
<!-- HEADER STRECH TO CONTENTPRESENTER OF DATAGRIDCOLUMNHEADER -->
<Grid x:Name="ContainerFilterGrid"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContentPresenter}, Mode=FindAncestor, AncestorLevel=1}, Path=ActualWidth}"
HorizontalAlignment="Stretch"
Background="Transparent">
<!-- HEADER/BUTTON -->
<Grid x:Name="GridHeaderButton"
ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- RENDER THE HEADER TEXT -->
<TextBlock Grid.Column="0"
Text="{Binding}" />
<!-- FILTER BUTTON -->
<Button Name="filterButton"
Grid.Column="1"
Width="19"
Height="19"
Background="Transparent"
BorderBrush="DarkGray"
BorderThickness="1"
Command="{x:Static control:FilterDataGrid.ShowFilter}"
Cursor="Hand"
Opacity="0.5"
OverridesDefaultStyle="True"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Padding="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Path x:Name="PathFilterIcon"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="{StaticResource Filter}"
Fill="DarkSlateGray"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Grid>
<!-- POPUP -->
<Popup Name="FilterPopup"
AllowsTransparency="True"
IsOpen="False"
PlacementTarget="{Binding ElementName=ContainerFilterGrid}"
StaysOpen="{StaticResource StayOpen}">
<Border x:Name="PopUpBorder"
Padding="0"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
BorderBrush="LightGray"
BorderThickness="1">
<Grid x:Name="SizableContentGrid"
MinWidth="{StaticResource PopupWidth}"
MinHeight="{StaticResource PopupHeight}"
ShowGridLines="False"
ZIndex="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"
MinWidth="32" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- BUTTON CLEAR FILTER -->
<Button x:Name="ClearFilterBnt"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="2,10,2,2"
Padding="4"
HorizontalAlignment="Stretch"
Command="{x:Static control:FilterDataGrid.RemoveFilter}"
Content="{Binding Path=Content, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, UpdateSourceTrigger=PropertyChanged}"
FontSize="13"
OverridesDefaultStyle="True">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground"
Value="DarkSlateGray" />
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="BorderContent"
Padding="{TemplateBinding Padding}"
BorderBrush="Transparent"
BorderThickness="0"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Grid x:Name="ContentGrid"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="0"
Width="16"
Margin="0,0,10,0"
Data="{StaticResource FilterDelete}"
Fill="{TemplateBinding Foreground}"
Stretch="Uniform" />
<TextBlock x:Name="ContentPresenter"
Grid.Column="1"
HorizontalAlignment="Stretch"
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StringFormatConverter}">
<MultiBinding.Bindings>
<Binding Path="Translate.Clear"
TargetNullValue=""
UpdateSourceTrigger="PropertyChanged" />
<Binding ElementName="ClearFilterBnt"
Path="Content" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="BorderContent"
Property="Background"
Value="#F0F0F0" />
</Trigger>
<Trigger Property="IsPressed"
Value="True">
<Setter TargetName="BorderContent"
Property="Background">
<Setter.Value>
<SolidColorBrush Opacity="0.8"
Color="LightGray" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled"
Value="False">
<Setter Property="Foreground">
<Setter.Value>
<SolidColorBrush Opacity="0.5"
Color="DarkSlateGray" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
<!-- SEPARATOR -->
<Separator Grid.Row="3"
Grid.Column="1"
Margin="0,2"
Background="LightGray" />
<!-- SEARCH BOX -->
<Border Grid.Row="4"
Grid.Column="1"
Margin="0,4,4,2"
Padding="0,2"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="LightGray"
BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="SearchBox"
Grid.Column="0"
Margin="0"
Padding="2,0,0,0"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
AcceptsReturn="False"
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}"
Focusable="True"
FontSize="13"
MaxLength="20"
Style="{StaticResource PlaceHolder}"
Tag="{Binding Translate.Contains}">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{x:Static control:FilterDataGrid.ApplyFilter}" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</Border>
<!-- ICON (current filter is set) -->
<Path x:Name="PathIsFiltered"
Grid.Row="5"
Grid.Column="0"
Width="19"
Height="20"
Margin="0,5,0,0"
VerticalAlignment="Top"
Data="{StaticResource FilterChecked}"
Fill="DarkSlateGray"
Stretch="Fill"
Stroke="DarkSlateGray"
StrokeThickness="0.2">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Visibility"
Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ClearFilterBnt, Path=IsEnabled}"
Value="True">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
<!-- LISTBOX / TREEVIEW -->
<Border Grid.Row="5"
Grid.Column="1"
Margin="0,4,4,4"
BorderThickness="0">
<Grid x:Name="GridItemControl">
<ListBox x:Name="PopupListBox"
Grid.Row="0"
Padding="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="LightGray"
BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Visibility="Collapsed">
<ListBox.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type ListBoxItem}}"
TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment"
Value="Left" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<!--
ContentStringFormat="{}{0}"
Content="{Binding Label, ConverterCulture=en-US}"
ContentStringFormat="F2, en-US"
the value of the label is a string, so it cannot be formatted
-->
<DataTemplate DataType="{x:Type control:FilterItem}">
<CheckBox x:Name="CheckBox"
Width="Auto"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Command="{x:Static control:FilterDataGrid.IsChecked}"
CommandParameter="{Binding}"
Content="{Binding Label}"
FontWeight="Normal"
IsChecked="{Binding IsChecked}"
IsThreeState="False" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Level}"
Value="1">
<Setter TargetName="CheckBox"
Property="Margin"
Value="4,0,0,0" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TreeView x:Name="PopupTreeview"
Grid.Row="0"
BorderBrush="LightGray"
BorderThickness="1"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Visibility="Collapsed">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="control:FilterItem"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CheckBoxTree"
VerticalAlignment="Center"
Focusable="False"
IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" />
<ContentPresenter Margin="2"
Content="{Binding Label}" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Level}"
Value="1">
<Setter TargetName="CheckBoxTree"
Property="Margin"
Value="4,0,0,0" />
</DataTrigger>
</DataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type TreeViewItem}}"
TargetType="{x:Type TreeViewItem}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="IsExpanded"
Value="False" />
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="VerticalContentAlignment"
Value="Stretch" />
<Setter Property="Visibility"
Value="Visible" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Border>
<!-- OK/CANCEL BUTTON -->
<UniformGrid Grid.Row="6"
Grid.Column="1"
Margin="0,6,4,6"
HorizontalAlignment="Right"
Background="Transparent"
Columns="2"
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type control:FilterDataGrid}}}">
<Button Width="100"
Margin="0"
HorizontalAlignment="Left"
Command="{x:Static control:FilterDataGrid.ApplyFilter}"
Content="{Binding Translate.Ok}" />
<Button Width="100"
Margin="6,0,0,0"
HorizontalAlignment="Right"
Command="{x:Static control:FilterDataGrid.CancelFilter}"
Content="{Binding Translate.Cancel}" />
</UniformGrid>
<!-- RESIZE GRIP -->
<Thumb x:Name="PopupThumb"
Grid.Row="7"
Grid.Column="0"
Grid.ColumnSpan="2"
Width="20"
Height="Auto"
Margin="0,0,2,2"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Thumb.Style>
<Style TargetType="{x:Type Thumb}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="resizeVisual"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Background="Transparent"
DockPanel.Dock="Right"
SnapsToDevicePixels="True"
UseLayoutRounding="True">
<Path Width="12"
Height="12"
Margin="0"
Data="{StaticResource GripSizeIcon}"
Stretch="None"
Stroke="LightSlateGray"
StrokeThickness="1" />
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Cursor"
Value="SizeNWSE" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
</Thumb.Style>
</Thumb>
</Grid>
</Border>
</Popup>
</Grid>
</DataTemplate>
</ResourceDictionary>

View File

@@ -0,0 +1,18 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:control="clr-namespace:FSI.Lib.Wpf.Ctrls.FilterDataGrid">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/FilterDataGrid;component/Themes/FilterDataGrid.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--
EXTERNAL ACCESS FOR CUSTOMIZE DEFAULT STYLE
SEE FILTERDATAGRID DEFAULT STYLE
-->
<Style x:Key="{ComponentResourceKey {x:Type control:FilterDataGrid},
FilterDataGridStyle}"
BasedOn="{StaticResource {x:Type control:FilterDataGrid}}"
TargetType="{x:Type control:FilterDataGrid}" />
</ResourceDictionary>