Custome StackPanel Prism RegionAdapter to support Ordering - silverlight

I have the following implementation of RegionAdapter for a StackPanel but I need strict ordering of items I associate with a region can anyone help?
I want Views that Register themselves to the Region to be able to control there position maybe an index number of some sort
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}

How to tackle this greatly depends on whether the sorting refers to (a) the type of the view or (b) to the instance of the view. The former would be the case if you only wanted to specify that for example Views of type ViewA should be above Views of type ViewB. The latter is the case if you want to specify how several concrete instances of the same view type are sorted.
A. Sort type wise
On option is to implement a custom attribute, something like OrderIndexAttribute, which exposes an integer property:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public OrderIndexAttribute : Attribute
{
public int Index { get; }
public OrderIndexAttribute(int index)
{
Index = index;
}
}
Mark your view class with that attribute:
[OrderIndex(2)]
public ViewA : UserControl
{...}
Get the attribute of the type when adding the view to the region:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var viewType = element.GetType();
var viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
B. Sort instance wise
Make your views implement an interface:
public interface ISortedView
{
int Index { get; }
}
On adding the view to the region, try casting the inserted view to the interface, read the index and then do the same as above:
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
// Get index for view
var sortedView = element as ISortedView;
if (sortedView != null)
{
var viewIndex = sortedView.Index;
// This method needs to iterate through the views in the region and determine
// where a view with the specified index needs to be inserted
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, sortedView);
}
else
{ // Add at the end of the StackPanel or reject adding the view to the region }
}

I found Marc's "A. Sort type wise" case to be very helpful for my situation. I needed to sort the views into the region by using the OrderIndexAttribute and still be able to add a view if it did not actually have the OrderIndexAttribute.
As you will see below, I did this by keeping track of the view indexes in a List. The insertion index of a view without the attribute is defaulted to zero so that it sorts to the front(or top) of the StackPanel.
This original post is rather old, but maybe someone will stumble upon it as I did and will find my contribution to be helpful. Refactoring suggestions are welcome. :-)
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{
private readonly List<int> _indexList;
public StackPanelRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
_indexList = new List<int>();
}
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
var viewType = element.GetType();
// Get the custom attributes for this view, if any
var customAttributes = viewType.GetCustomAttributes(false);
var viewIndex = 0; // Default the viewIndex to zero
// Determine if the view has the OrderIndexAttribute.
// If it does have the OrderIndexAttribute, get its sort index.
if (HasThisAttribute(customAttributes))
{
viewIndex= viewType.GetCustomAttribute<OrderIndexAttribute>().Index;
}
// Get the insertion index
var insertionIndex = GetInsertionIndex(viewIndex);
regionTarget.Children.Insert(insertionIndex, element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
private static bool HasThisAttribute(IReadOnlyList<object> customAttributes)
{
// Determine if the view has the OrderIndexAttribute
if (customAttributes.Count == 0) return false;
for (var i = 0; i < customAttributes.Count; i++)
{
var name = customAttributes[i].GetType().Name;
if (name == "OrderIndexAttribute") return true;
}
return false;
}
private int GetInsertionIndex(in int viewIndex)
{
// Add the viewIndex to the index list if not already there
if (_indexList.Contains(viewIndex) == false)
{
_indexList.Add(viewIndex);
_indexList.Sort();
}
// Return the list index of the viewIndex
for (var i = 0; i < _indexList.Count; i++)
{
if (_indexList[i].Equals(viewIndex))
{
return i;
}
}
return 0;
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}

The answer from Marc and R.Evans helped me to create my own a little more generic RegionAdapter with the following improvements:
uses ViewSortHint to be compatible with Prism 6
Prism 7 / .Net 5 compatible
Helper Class for use in multiple Adapters
less code
Adapt method:
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
foreach (FrameworkElement item in e.NewItems)
{
regionTarget.Children.Insert(regionTarget.Children.GetInsertionIndex(item), item);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (UIElement item in e.OldItems)
{
if (regionTarget.Children.Contains(item))
{
regionTarget.Children.Remove(item);
}
}
};
}
Helper/Extension:
internal static int GetInsertionIndex(this IList items, in object newItem)
{
// Return the list index of the viewIndex
foreach (object item in items)
{
var currentIndex = item.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
var intendedIndex = newItem.GetType().GetCustomAttribute<ViewSortHintAttribute>()?.Hint ?? "0";
if (currentIndex.CompareTo(intendedIndex) >= 0)
return items.IndexOf(item);
}
// if no greater index is found, insert the item at the end
return items.Count;
}

Related

How to "Navigate" with a Prism Custom adapter?

I've a Prism Custom Region Adapter, to display every view in a different tab of our DevExpress "DocumentGroup".
In order to do this, I've the following RegionAdapter:
public class DocumentGroupRegionAdapter : RegionAdapterBase<DocumentGroup>
{
public DocumentGroupRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
: base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, DocumentGroup regionTarget)
{
region.Views.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel documentPanel = new DocumentPanel {Content = element, DataContext = element.DataContext};
regionTarget.Items.Add(documentPanel);
}
}
};
}
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
With AllActiveRegion being:
public class AllActiveRegion : Region
{
public override IViewsCollection ActiveViews
{
get { return Views; }
}
public override void Deactivate(object view)
{
throw new InvalidOperationException(Resources.DeactiveNotPossibleException);
}
}
And we were registering several View for this region:
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Layout.RootView));
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Configure.RootView));
_regionManager.RegisterViewWithRegion(Regions.MainSections, typeof(Views.Dashboard.RootView));
It worked fine up until now, but now, on certain options, we need to activate one of the tab. This would be done by calling item.IsActive = true.
How do I specify which item I want to navigate too?
What should I override to set this active item?
For the interested ones, I had to do several things to resolve the issue:
Switch to the SingleActiveRegion instead of the AllActiveRegion. The AllActiveRegion doesn't keep track of the selected(active) item, basically, the region.Views = region.ActiveViews. So you cannot register to ActiveViews changes.
Listen to the region.ActiveViews.CollectionChanged event. When it fires, I've to activate the DevExpress component
Listen to the DevExpress component for when the user clicks on the tabs. This is required, because otherwise Prism might think the control is already active and not active it again.
The code look like this:
public class DocumentGroupRegionAdapter : RegionAdapterBase<DocumentGroup>
{
public DocumentGroupRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory)
: base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, DocumentGroup regionTarget)
{
region.Views.CollectionChanged += (_, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel documentPanel = new() {Content = element, DataContext = element.DataContext};
regionTarget.Items.Add(documentPanel);
}
}
};
region.ActiveViews.CollectionChanged += (_, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in args.NewItems)
{
DocumentPanel existingItem = regionTarget.Items.Cast<DocumentPanel>().FirstOrDefault(i => i.Content == element);
if (existingItem != null)
{
existingItem.IsActive = true;
}
}
}
};
regionTarget.SelectedItemChanged += ((_, args) =>
{
region.Activate(((DocumentPanel)args.Item).Content);
});
}
protected override IRegion CreateRegion()
{
return new SingleActiveRegion();
}
}
Now that I've seen the source code of Prism, I think writing a custom Region could maybe also do the job, I'm not totally sure when the Activate() is called

Select all combo boxes on winform by LINQ [duplicate]

I have multiple pictureboxes and I need to load random images into them during runtime. So I thought it would be nice to have a collection of all pictureboxes and then assign images to them using a simple loop. But how should I do it? Or maybe are there any other better solutions to such problem?
Using a bit of LINQ:
foreach(var pb in this.Controls.OfType<PictureBox>())
{
//do stuff
}
However, this will only take care of PictureBoxes in the main container.
You could use this method:
public static IEnumerable<T> GetControlsOfType<T>(Control root)
where T : Control
{
var t = root as T;
if (t != null)
yield return t;
var container = root as ContainerControl;
if (container != null)
foreach (Control c in container.Controls)
foreach (var i in GetControlsOfType<T>(c))
yield return i;
}
Then you could do something like this:
foreach (var pictureBox in GetControlsOfType<PictureBox>(theForm)) {
// ...
}
If you're at least on .NET 3.5 then you have LINQ, which means that since ControlCollection implements IEnumerable you can just do:
var pictureBoxes = Controls.OfType<PictureBox>();
I use this generic recursive method:
The assumption of this method is that if the control is T than the method does not look in its children. If you need also to look to its children you can easily change it accordingly.
public static IList<T> GetAllControlsRecusrvive<T>(Control control) where T :Control
{
var rtn = new List<T>();
foreach (Control item in control.Controls)
{
var ctr = item as T;
if (ctr!=null)
{
rtn.Add(ctr);
}
else
{
rtn.AddRange(GetAllControlsRecusrvive<T>(item));
}
}
return rtn;
}
A simple function, easy to understand, recursive, and it works calling it inside any form control:
private void findControlsOfType(Type type, Control.ControlCollection formControls, ref List<Control> controls)
{
foreach (Control control in formControls)
{
if (control.GetType() == type)
controls.Add(control);
if (control.Controls.Count > 0)
findControlsOfType(type, control.Controls, ref controls);
}
}
You can call it on multiple ways.
To get the Buttons:
List<Control> buttons = new List<Control>();
findControlsOfType(typeof(Button), this.Controls, ref buttons);
To get the Panels:
List<Control> panels = new List<Control>();
findControlsOfType(typeof(Panel), this.Controls, ref panels);
etc.
Here's another version since the existing provided ones weren't quite what I had in mind. This one works as an extension method, optionally, and it excludes checking the root/parent container's type. This method is basically a "Get all descendent controls of type T" method:
public static System.Collections.Generic.IEnumerable<T> ControlsOfType<T>(this System.Web.UI.Control control) where T: System.Web.UI.Control{
foreach(System.Web.UI.Control childControl in control.Controls){
if(childControl is T) yield return (T)childControl;
foreach(var furtherDescendantControl in childControl.ControlsOfType<T>()) yield return furtherDescendantControl;
}
}
public static List<T> FindControlByType<T>(Control mainControl,bool getAllChild = false) where T :Control
{
List<T> lt = new List<T>();
for (int i = 0; i < mainControl.Controls.Count; i++)
{
if (mainControl.Controls[i] is T) lt.Add((T)mainControl.Controls[i]);
if (getAllChild) lt.AddRange(FindControlByType<T>(mainControl.Controls[i], getAllChild));
}
return lt;
}
This to me is by far the easiest. In my application, I was trying to clear all the textboxes in a panel:
foreach (Control c in panel.Controls)
{
if (c.GetType().Name == "TextBox")
{
c.Text = "";
}
}
Takes Control as container into account:
private static IEnumerable<T> GetControlsOfType<T>(this Control root)
where T : Control
{
if (root is T t)
yield return t;
if (root is ContainerControl || root is Control)
{
var container = root as Control;
foreach (Control c in container.Controls)
foreach (var i in GetControlsOfType<T>(c))
yield return i;
}
}

Detecting if an ObservableCollection has been modified

I have a DataGrid nested in a DockPanel. The DockPanel serves as a data context:
DockPanel1.DataContext = GetData();
The GetData() method returns an ObservableCollection.
The ObservableCollection can be modified in the DataGrid as well as in a few textboxes nested in the DockPanel. I also navigate through the collection using a DataView.
I'd like to detect if the collection has been modified and warn a user when he/she tries to close the application without saving data.
Is there any built-in mechanism that I could use (a kind of "IsDirty" flag on the collection or on the view)? If not, I guess I will have to monitor all the controls and detect any changes manually.
Thanks,
Leszek
In order to detect changes in the collection itself, you would have to attach a CollectionChanged handler. If you also need to detect changes in the objects contained in the collection, you would have to attach PropertyChanged handlers to every object (provided that the objects implements INotifyPropertyChanged).
An implementation would basically look like this:
var collection = GetData();
collection.CollectionChanged += OnCollectionChanged;
...
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
RemovePropertyChanged(e.OldItems);
AddPropertyChanged(e.NewItems);
break;
}
...
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
...
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnPropertyChanged;
}
}
}
To elaborate a bit on Clemens' answer above, here's the simple way to use these events (on the collection, and on the contained items) to implement an IsDirty flag such as you described:
public class DirtyCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
private bool isDirty = false;
public bool IsDirty
{
get { return this.isDirty; }
}
public void Clean()
{
this.isDirty = false;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// We aren't concerned with how the collection changed, just that it did.
this.isDirty = true;
// But we do need to add the handlers to detect property changes on each item.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
this.AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
this.RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
this.RemovePropertyChanged(e.OldItems);
this.AddPropertyChanged(e.NewItems);
break;
}
base.OnCollectionChanged(e);
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnItemPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// A property of a contained item has changed.
this.isDirty = true;
}
}
The code should be fairly self-explanatory.
You can, of course, remove the "where T : INotifyPropertyChanged" to allow objects that don't implement that interface to be stored in the collection, but then you won't be notified of any property changes on them, as without that interface, they can't notify you of them.
And should you want to keep track of not only that the collection is dirty but also how, some additions in OnCollectionChanged and OnItemPropertyChanged to record the information passed in the event args would do that nicely.

Winforms bind SelectedItems in listbox

Using the standard listbox I would like to bind it to a set of objects and update a bound items collection to include those selected.
So I have something like:
_pContext = new BindingSource();
_pContext.DataSource = _gpContext;
_pContext.DataMember = "ParentEntities";
_AllChildrenListBox.DataSource = _container.ChildEntities;
_AllChildrenListBox.DataBindings.Add("MySelectedItems", _pContext, "ChildEntities", false);
_allChildrenListBox is the listbox. I created a new listbox type inheriting from ListBox so I could create an alternative SelectedItems property which would then encapsulate the logic to set/unset the items.
So the question in a nutshell is: In the above, ChildEntities is a collection of "ChildEntity" objects. My list box contains all possible ChildEntity objects and I want the elements in ChildEntities to be selected, and updated when selection changed.
I have found a way around this. It's a little ugly at the moment, but works. I can refine it later.
So first bit was the binding itself.
_AllChildrenListBox.DataBindings.Add("SelectedItems2", _pContext, "ChildEntities2", true);
The key here seemed to be setting the formatting parameter to true.
Next I needed something in SelectedItems2 that would do my dirty work. This is a mess, but works. Here's the full class:
public class MyListBox : ListBox
{
private IEnumerable<object> _original;
public IEnumerable<object> SelectedItems2
{
get
{
return UpdateSet();
}
set
{
SelectItems(value);
}
}
private IEnumerable<object> UpdateSet()
{
var listSource = _original as IListSource;
IList list = null;
if (listSource != null)
{
list = listSource.GetList();
}
var iList = _original as IList;
if (list == null && iList != null)
{
list = iList;
}
if (list == null)
{
return _original;
}
foreach (var item in SelectedItems)
{
if (!list.Contains(item))
{
list.Add(item);
}
}
foreach (var item in _original.ToList())
{
if (!SelectedItems.Contains(item))
{
list.Remove(item);
}
}
return _original;
}
private void SelectItems(IEnumerable<object> items)
{
_original = items;
var hashset = new HashSet<object>();
foreach (var item in items)
{
hashset.Add(item);
}
for(var i=0;i<Items.Count;i++)
{
SetSelected(i, hashset.Contains(Items[i]));
}
}
}
Essentially I'm breaking all the rules and assuming IEnumerable hides a more tasty list based interface, if it does it uses that to changes the underlying set.
Finally, I'm using EF (Entity Framework) and you can't call the setter of a collection property, so my entity currently looks like this: Notice how I'm duplicating all the list change operations which is totally unnecessary, but I did say this was just the first working go.
public partial class ParentEntity
{
public IEnumerable<object> ChildEntities2
{
get
{
return new List<object>(ChildEntities);
}
set
{
if (value == null)
{
return;
}
foreach (var item in ChildEntities.ToList().Where(item => !value.Contains(item)))
{
ChildEntities.Remove(item);
}
foreach (var item in value)
{
var cItem = item as ChildEntity;
if (cItem != null)
{
if (!ChildEntities.Contains(item as ChildEntity))
{
ChildEntities.Add(item as ChildEntity);
}
}
}
}
}
}

WPF DataGrid MultiColumn sort without shift key

The WPF DataGrid has a default behavior of allowing multicolumn sorts when the user shift-clicks on multiple column headers. Is there a way to change this behavior so that the Shift key is not required? I have already handled the sorting event on the datagrid so that each column will cycle between the three sorting states (Ascending, Descending, and No Sort) and this is all working as expected as long as the shift key is held down, but I would like to make it so that the DataGrid does not reset the sorting on all other columns if the user clicks a column header to add a sort without pressing shift.
I have found a solution that seems to be a bit of a hack, but it works. This article got me pointed in the right direction: http://blogs.msdn.com/b/vinsibal/archive/2008/08/29/wpf-datagrid-tri-state-sorting-sample.aspx?PageIndex=2. That brought me to the understanding the the SortDirection of each column is not really tied to the ItemsSource SortDescriptions in any way. So what I did was subscribe to the Sorting event of the Datagrid and reset the SortDirection of each column that is referenced in the ItemsSource SortDescriptions collection. Apparently, not pressing shift clears the sortdirection of each column, but doesn't reset the SortDescriptions.
I was dealing with the same problem, but no one showed the code that can solve this it. The question is old, but I hope the solution will be useful for seekers.
(DataGrid.Items.SortDescriptions as INotifyCollectionChanged).CollectionChanged += OnGridCollectionChanged;
private void OnGridCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
var sortingCollection = (SortDescriptionCollection)sender;
foreach (var sortDesc in sortingCollection)
{
foreach (var column in SignaturesInImagingGrid.Columns)
{
if (column.SortMemberPath.Equals(sortDesc.PropertyName))
{
column.SortDirection = sortDesc.Direction;
}
}
}
}
<DataGrid Sorting="GridMultiColumnSortingEvent">
public static void GridMultiColumnSortingEvent(object sender, DataGridSortingEventArgs e)
{
var dgSender = (DataGrid)sender;
var cView = CollectionViewSource.GetDefaultView(dgSender.ItemsSource);
ListSortDirection direction = ListSortDirection.Ascending;
if (ContainsSortColumn((DataGrid)sender, e.Column.SortMemberPath))
{
if (e.Column.SortDirection == null)
{
direction = ListSortDirection.Ascending;
ChangeSortColumn((DataGrid)sender, e.Column, direction);
}
else if (DirectionForColumn(cView, e.Column) == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
ChangeSortColumn((DataGrid)sender, e.Column, direction);
}
else if (DirectionForColumn(cView, e.Column) == ListSortDirection.Descending)
{
e.Column.SortDirection = null;
cView.SortDescriptions.Remove(cView.SortDescriptions.Where(item => item.PropertyName.Equals(e.Column.SortMemberPath)).FirstOrDefault());
cView.Refresh();
}
}
else
{
AddSortColumn((DataGrid)sender, e.Column.SortMemberPath, direction);
cView.Refresh();
}
e.Handled = true;
}
private static ListSortDirection DirectionForColumn(ICollectionView cView, DataGridColumn column) =>
cView.SortDescriptions.Where(item => item.PropertyName.Equals(column.SortMemberPath))
.FirstOrDefault()
.Direction;
private static void AddSortColumn(DataGrid sender, string sortColumn, ListSortDirection direction)
{
var cView = CollectionViewSource.GetDefaultView(sender.ItemsSource);
cView.SortDescriptions.Add(new SortDescription(sortColumn, direction));
foreach (var col in sender.Columns.Where(x => x.SortMemberPath == sortColumn))
{
col.SortDirection = direction;
}
}
private static void ChangeSortColumn(DataGrid sender, DataGridColumn column, ListSortDirection direction)
{
var cView = CollectionViewSource.GetDefaultView(sender.ItemsSource);
string sortColumn = column.SortMemberPath;
foreach (var sortDesc in cView.SortDescriptions.ToList())
{
if (sortDesc.PropertyName.Equals(sortColumn))
{
cView.SortDescriptions.Remove(sortDesc);
break;
}
}
AddSortColumn(sender, sortColumn, direction);
}
private static bool ContainsSortColumn(DataGrid sender, string sortColumn)
{
var cView = CollectionViewSource.GetDefaultView(sender.ItemsSource);
foreach (var sortDesc in cView.SortDescriptions.ToList())
{
if (sortDesc.PropertyName.Equals(sortColumn))
return true;
}
return false;
}

Resources