WPF Listview : Column reorder event? - wpf

I need to sync the column order of two ListViews event when the user changes the order. But it seems there is not a Column reorder event.
For the moment I just did a AllowsColumnReorder="False" but that is not a permanent solution. While searching the net I found many people with the same problem but no solution. What can be done?

I'm not sure it works, but you could probably take advantage of the fact that GridView.Columns is an ObservableCollection : you could subscribe to the CollectionChanged event and handle the case where Action = Move
GridView gridView = (GridView)listView.View;
gridView.Columns.CollectionChanged += gridView_CollectionChanged;
private void gridView_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Move)
{
string msg = string.Format("Column moved from position {0} to position {1}", e.OldIndex, e.NewIndex);
MessageBox.Show(msg);
}
}

Related

WPF ListView with ItemsSource: how to know when model adds item

Question: What is "proper" way of letting the user control's "view model" (.xaml.cs file) know that a ListViewItem has been added to a ListView? Note that this post is addresses a different problem.
Details:
I have a UserControl which contains a ListView and a DataContext:
The ListView has an ItemsSource={Binding ActionLogEntries}
ActionLogEntries is an ObservableCollection property in the DataContext
The data context adds items to the ListView when certain things happen.
But there isn't a ListView.ItemAdded event. There is a CollectionChanged event on ObservableCollection in data context but the view model's handler of this event could get called before the item is added to the ListView so this doesn't seem like a good strategy.
FYI: This came up because when items are added to the ListView, it doesn't automatically scroll to the newly added item, which is behavior I have to add. Presumably I'd use ScrollIntoView after that.
So there are at least two ways of skinning this cat:
do as explained by Clemens in comment to my question
do as in this post by WPF Mentor
Solution 1 seems more natural for the event subscription, since you don't need to cast; also IntelliSense doesn't show class members of implemented interfaces without cast, so for Solution 2 you have to remember to look at what interfaces are implemented and check for events there too. Here is what the subscription looks like for each solution:
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// Solution 1, subscription:
xActionListView.ItemContainerGenerator.ItemsChanged +=
new ItemsChangedEventHandler(ActionLog_ItemsChanged);
// Solution 2, subscription:
((INotifyCollectionChanged)xActionListView.Items).CollectionChanged +=
new NotifyCollectionChangedEventHandler(ActionListView_CollectionChanged);
}
But solution 2 has easier to use event arg in handler:
// Solution 1, handler:
private void ActionLog_ItemsChanged(object sender, ItemsChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Solution 1, scroll the new item into view
xActionListView.ScrollIntoView(
xActionListView.Items[e.Position.Index + e.Position.Offset]);
}
}
// Solution 2, handler:
private void ActionListView_CollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Solution 2, scroll the new item into view
xActionListView.ScrollIntoView(e.NewItems[0]);
}
}
It looks like in some circumstances, one solution may be more appropriate than the other: the event data may be easier to use in one or the other based on what data you need.

Silverlight DataGrid MouseLeftButtonDown event not raised when clicking on rows

Note: I have found a solution to my problem so I am posting this for reference purposes, although I would be happy to be educated with a better solution.
I'm trying to provide double click functionality on a Silverlight DataGrid by hooking into the UIElement.MouseLeftButtonDown but when I subscribe to the DataGrid.MouseLeftButtonDown using XAML or the DataGrid.MouseLeftButtonDown += syntax, my event handler is not called when I click on the rows within the DataGrid. If I click on the Header, the event is raised.
If I subscribe to the same event at the parent UserControl level, the event handler is called successfully as you would expect based on Silverlight RoutedEvents but then I have to detect whether the click occurred on the DataGrid or somewhere else.
If I subscribe to the event using this UIElement.AddHandler syntax, as shown below, then it works as expected based on the handledEventsToo: true parameter.
dataGrid.AddHandler(UIElement.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(dataGrid_MouseLeftButtonDown)
, handledEventsToo: true);
It seems that the DataGrid implementation is marking these events as handled, prevent event bubbling, by default in one of the child UIElements, which is not what I expected initially. With more thought I can see that the click behaviour drives all sorts of things (select item, edit field etc.) so perhaps the implementation makes sense.
I had the same problem and I used MouseLeftButtonUp which fires the event but the clickcount value is always 1.
Here is the fix for that:
private const int MOUSE_SENSITIVITY = 300;
private DateTime _previousClick;
private void exceptionsDataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
DataGrid dg = (sender as DataGrid);
DateTime current=DateTime.Now;
LoggerService.Exception exception = (LoggerService.Exception)dg.SelectedItem;
if (_previousClick != null)
{
TimeSpan clickSpan = current - _previousClick;
if (clickSpan.TotalMilliseconds < MOUSE_SENSITIVITY)
{
MessageBox.Show("You double clicked!");
}
}
_previousClick = current;
}

Initial DataGrid Sorting

I have a user control that contains a WPF toolkit DataGrid. This control is used in many different places in my app. The grid has no knowledge as to the type of data that will show. Is there a way to initially sort the grid by the first column in ascending order no matter what data the grid is populated with? I don't think I can use a CollectionViewSource because I don't know the PropertyName of the property bound to the first column.
You could hook to an event:
dataGrid.AutoGeneratedColumns += dataGrid_AutoGeneratedColumns;
and sort the first column:
void dataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
var firstCol = dataGrid.Columns.First();
firstCol.SortDirection = ListSortDirection.Ascending;
dataGrid.Items.SortDescriptions.Add(new SortDescription(firstCol.SortMemberPath, ListSortDirection.Ascending));
}
I would suggest you to create a derived separate DataGrid control, placing this logic there and using the new control to avoid repeating the code every time.
public class CustomDataGrid : DataGrid
{
public DynamicDataGrid()
{ ... }
...
}

Datagrid in WPF and sql datatable vs other methods

Here is what i tried to do.. I want to provide an in place editing of products within a datagrid. Firstly i wrapped into a ObservableCollection a List<Product> and implemented INotifyPropertyChanged interface for each POCO. Undoing changes and tracking what has changed in order to commit seems hard and i had lots of problems.. Also i feel that creating so many event handlers for every single poco when a property changed is not good strange...
So i am asking does DataTable solve these problems ? Even it solves what about validation ?It doesnt know anything about the POCO.... Are any other better solutions for this ??
The property change handlers aren't that bad. Here's an example:
// Hook up a CollectionChanged event
ProductCollection.CollectionChanged += ProductCollection_Changed;
// In the Collection Changed event, hook up a PropertyChanged event
void ProductCollection_Changed(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach(Product item in e.NewItems)
item.PropertyChanged += Product.PropertyChanged;
}
if (e.OldItems != null)
{
foreach(Product item in e.OldItems)
item.PropertyChanged -= Product.PropertyChanged;
}
}
// In the Product property changed event, do something.
// Usually I only use this for raising the CollectionChanged event when
// a property of an object inside a collection changes.
void Product_Changed(object sender, PropertyChangedEventArgs e)
{
}
Personally, I would prefer to have each Product track it's own changes, instead of tracking them in the ViewModel. When a product first gets created, keep a copy of the original data, and provide something like a UndoChanges() method that simply reloads the original data.
To track changes on individual properties, I would do that in the set method of each Product property. This is because the PropertyChanged event can be raised manually, and doesn't necessarily mean that the property has changed.
private int _someValue;
public int SomeValue
{
get { return _someValue; }
set
{
if (value != _someValue)
{
// Track what's changed here.
// How you do it is based on what you want to track
if (!ChangedProperties.Keys.Contains("SomeValue"))
{
ChangedProperties.Add(
new KeyValuePair<string, object>("SomeValue", _someValue));
}
_someValue = value;
RaisePropertyChanged("SomeValue");
}
}
}
DataTable do solve a few things...
When you are not sure how many columns you are going to have.
When there are frequent edits, the edits take place onto the data table and if the data table has constraints then they will error out for invalid entries. So some level of validation and data integrity is maintained.
Being queryable, the data tables produce faster results with sorting and filtering with their DefaultView i.e. DataView.
Having said that ...
Frequent updates are better achieved by observable collection with each item having implemented INotifypropertyChanged.
Validations using Bindings are easily achievable.
Datagrid does provide some more features for object model of collections than data tables.
So ultimately its your choice. However I dont mind observable collections and INotifyPropertyChanged notifications as they seem to get the best out of WPF DataGrid... styling and performance wise.

Where is WPF DataGrid DataBindingComplete event?

I need to take some action (e.g. make some cells readonly based on some other cells) after data biinding is completed. In WinForm DataGridView, I used to do it in DataBindingComplete event. However, I couldn't find such an event in WPF DataGrid. What else can I use?
This is what I figured out: DataContextChanged event is the right event to use. The only problem is that the datagrid is not quite ready to be consumed in my code inside this event. However, it works fine if I use Dispatcher.BeginInvoke like this:
Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => DoSomethingWithGrid()));
Can anybody explain why this is necessary?
Actually, when dealing with WPF DataGrid, I had to use Dispatcher in quite a few cases in order to make it work. Why?
DataContextChanged.
May be you are using threads and datagrid is not thread-safe as all UI components.
I wanted to color my rows depending on their property values, and I tried a lot of events (DataGrid.Initialized, DataContextChanged, AddingNewItem, RowLoaded etc.) along with the BeginInvoke thing, but nothing worked. Then i found:
Loaded
This event did the trick, as it allowed me to iterate through my rows and color them as I wanted.
private void SubjectsList_Loaded(object sender, RoutedEventArgs e)
{
Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => ColorMyRows()));
}
The CollorMyRows looks pretty similar to tihs:
private void ColorMyRows()
{
DataGridRow row = null;
for (int i = 0; i < SubjectsList.Items.Count; i++)
{
// get one row
row = SubjectsList.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;
if (myConditionIsFulfilled)
{
row.Background = Brushes.PaleGoldenrod; // black'n gold baby
row.ToolTip = "This item fulfills the condition.";
}
else
{
row.Background = Brushes.PaleGreen;
row.ToolTip = "This item does not.";
}
}
}
Note: If You have an ObservableCollection bound to a DataGrid, the index in the loop (index of a DataGrid's row) will correspond to the index in the collection :)
you can bind cells readonly property to a property that changes when other properties of the model changes.
I was thinking exactly like you while ago, but I started to think more in the model more than the view I am not interested in DataGrid but the list that is bounded to, you can do the same
I used it before in a similar situation
public class Model : INotifyPropertyChanged
{
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
RaisePropertyChanged("IsChecked");
RaisePropertyChanged("Visibilty");
}
}
public Visibility Visibilty
{
get
{
return IsChecked ? Visibility.Visible : Visibility.Hidden;
}
}
}
it was a checkbox in datagrid bound to IsChecked Property and other cells bounded to visibility and it worked for me.
hope this help you.
You can declare a BackgroundWorker and try to fill your GridView in the DoWork event and write your code in the RunWorkerCompleted event

Resources