MVVM multiple selection, keep after data reloading - c#

I'm using listview that must be able to cope with multiselection. To achieve it I use solution described at MultiSelection not working in ListView (Ankesh answer). It works fine. In my application some timer is working which retrieves devices status every 10 seconds and it assign ObservableCollection as ItemsSource to my ListView and then my selected rows are deselected.
I thought that when I store selected items before reloading data to temporary variable and assign it later it will work, but I have some problems which I can't diagnose.
This is my code (reloading data block):
if (SelectedMachines != null)
{
foreach (var selectedMachine in SelectedMachines)
{
oldSelected.Add(selectedMachine.Clone()); // clone objects
}
SelectedMachines.Clear();
}
Machines = Controller.GetAutomats(); // assign data
if (SelectedMachines != null) // restore selection
{
foreach (var selectedMachine in oldSelected)
{
SelectedMachines.Add(Machines.Where(t => t.Id == selectedMachine.Id).FirstOrDefault());
// at this point SelectedMachines has same elements count as before assign
}
}
Unfortunately such code clears selection after reloading data.

Related

Weird SharePoint ItemUpdating Behavior

I have a SharePoint list where I register a custom ItemUpdating event receiver but I am seeing some really strange behavior in this solution. This behavior occurs if I add any code to the event receiver other than base.ItemUpdating.
What happens is if I debug the event receiver I can see that properties.AfterProperties has all the values entered on the field and properties.ListItem has the original item. But once the ER finishes running and the page reloads nothing is saved and it just returns to what it was before I changed values. Even more weird, if I go and manually set the after properties similar to below it works and the updates are saved correctly. So basically the event receiver is making me responsible to do any changes to the item but this is not normal behavior for ItemUpdating. Does anyone have any idea what might cause this?
public override void ItemUpdating(SPItemEventProperties properties)
{
var recurringBefore = properties.ListItem.TryGetValue<bool>(Constants.CommonFields.Recurring_STATIC);
var recurringAfter = Convert.ToBoolean(properties.AfterProperties[Constants.CommonFields.Recurring_STATIC]);
//This loop is the horrible fix I have done to manually update the relevant fields but this shouldn't be necessary
var item = properties.ListItem;
foreach (SPField key in item.Fields)
{
if (item[key.InternalName] != properties.AfterProperties[key.InternalName] && key.CanBeDisplayedInEditForm && properties.AfterProperties[key.InternalName] != null)
{
//looping through and setting the AfterProperties to what they already are makes them save? If I don't do this nothing saves
properties.AfterProperties[key.InternalName] = properties.AfterProperties[key.InternalName].ToString();
}
}
if (!recurringBefore && recurringAfter &&
currWfStatus == Constants.WorkflowStatus.Processed)
{
//do some stuff
}
base.ItemUpdating(properties);
}
Is it because you are not saving current item at all, something like this:
item.update();

WPF ListView: Changing ItemsSource does not change ListView

I am using a ListView control to display some lines of data. There is a background task which receives external updates to the content of the list. The newly received data may contain less, more or the same number of items and also the items itself may have changed.
The ListView.ItemsSource is bound to an OberservableCollection (_itemList) so that changes to _itemList should be visible also in the ListView.
_itemList = new ObservableCollection<PmemCombItem>();
_itemList.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
L_PmemCombList.ItemsSource = _itemList;
In order to avoid refreshing the complete ListView I do a simple comparison of the newly retrieved list with the current _itemList, change items which are not the same and add/remove items if necessary. The collection "newList" contains newly created objects, so replacing an item in _itemList is correctly sending a "Refresh" notification (which I can log by using the event handler OnCollectionChanged of the ObservableCollection`)
Action action = () =>
{
for (int i = 0; i < newList.Count; i++)
{
// item exists in old list -> replace if changed
if (i < _itemList.Count)
{
if (!_itemList[i].SameDataAs(newList[i]))
_itemList[i] = newList[i];
}
// new list contains more items -> add items
else
_itemList.Add(newList[i]);
}
// new list contains less items -> remove items
for (int i = _itemList.Count - 1; i >= newList.Count; i--)
_itemList.RemoveAt(i);
};
Dispatcher.BeginInvoke(DispatcherPriority.Background, action);
My problem is that if many items are changed in this loop, the ListView is NOT refreshing and the data on screen stay as they are...and this I don't understand.
Even a simpler version like this (exchanging ALL elements)
List<PmemCombItem> newList = new List<PmemCombItem>();
foreach (PmemViewItem comb in combList)
newList.Add(new PmemCombItem(comb));
if (_itemList.Count == newList.Count)
for (int i = 0; i < newList.Count; i++)
_itemList[i] = newList[i];
else
{
_itemList.Clear();
foreach (PmemCombItem item in newList)
_itemList.Add(item);
}
is not working properly
Any clue on this?
UPDATE
If I call the following code manually after updating all elements, everything works fine
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
But of course this causes the UI to update everything which I still want to avoid.
After a change, you can use the following to refresh the Listview, it's more easy
listView.Items.Refresh();
This is what I had to do to get it to work.
MyListView.ItemsSource = null;
MyListView.ItemsSource = MyDataSource;
I know that's an old question, but I just stumbled upon this issue. I didn't really want to use the null assignation trick or the refresh for just a field that was updated.
So, after looking at MSDN, I found this article:
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged?redirectedfrom=MSDN&view=netframework-4.7.2
To summarize, you just need the item to implement this interface and it will automatically detect that this object can be observed.
public class MyItem : INotifyPropertyChanged
{
private string status;
public string Status
{
get => status;
set
{
OnPropertyChanged(nameof(Status));
status = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
So, the event will be called everytime someone changes the Status. And, in your case, the listview will add a handler automatically on the PropertyChanged event.
This doesn't really handle the issue in your case (add/remove).
But for that, I would suggest that you have a look at BindingList<T>
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.bindinglist-1?view=netframework-4.7.2
Using the same pattern, your listview will be updated properly without using any tricks.
You should not reset ItemsSource of ListView each time observable collection changed. Just set proper binding that will do your trick. In xaml:
<ListView ItemsSource='{Binding ItemsCollection}'
...
</ListView>
And in code-behind (suggest to use MVVM) property that will be responsible for holding _itemList:
public ObservableCollection<PmemCombItem> ItemsCollection
{
get
{
if (_itemList == null)
{
_itemList = new ObservableCollection<PmemCombItem>();
}
return _itemList;
}
}
UPDATE:
There is similar post which most probably will Answer your question: How do I update an ObservableCollection via a worker thread?
I found a way to do it. It is not really that great but it works.
YourList.ItemsSource = null;
// Update the List containing your elements (lets call it x)
YourList.ItemsSource = x;
this should refresh your ListView (it works for my UAP :) )
An alternative on Xopher's answer.
MyListView.ItemsSource = MyDataSource.ToList();
This refreshes the Listview because it's a other list.
Please check this answer:
Passing ListView Items to Commands using Prism Library
List view Items needs to notify about changes (done is setter)
public ObservableCollection<Model.Step> Steps
{
get { return _steps; }
set { SetProperty(ref _steps, value); }
}
and UpdateSourceTrigger need to be set in xaml
<Image Source="{Binding ImageData, UpdateSourceTrigger=PropertyChanged}" />

Wpf application and Threads

I have problem with my GUI and Threads.
The GUI contains DataGrid. Every X time the program do some query and getting a list of items that I want to fill into the DataGrid.
So far so good:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
}));
FindSelectionObject(); // <-- see next explanation.
}
When the user click on one of the objects in the datagrid, the line color changed (it works fine), but when the program reload the table,The painted line disappears (Becuse I clear and add new objects).
To deal with it, I created the function FindSelectionObject():
private void FindSelectionObject()
{
this.Dispatcher.Invoke((Action)(() =>
{
this.SelectedIndex = TaskListTable.Items.IndexOf((myObject)lastSelectionObject); //find index of the new object that equels to the last selection object.
var row = TaskListTable.ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as DataGridRow; //get the row with the index
row.Background = Brushes.LightGoldenrodYellow; //repaint
}));
}
The problem: Everything works fine, but sometimes when the program reloads, the line flashes per second and then highlighted back, and sometimes it's not painting it at all (untill the next reload).
I can't understand why this is happening. I think maybe the FindSelectionObject() begins to run before the loadTaskList() ends to invoke all and add the new objects into the datagrid.
But if so - Why? And how can I fix it?
In the bottom line, I want that after every reload the line re-paint immediately..
Thanks for any advice!
A few things to think about:
You should keep in mind that the DataGrid uses virtualization, which means that each item in your items source does not get its very own UI element. The UI elements are created to fill the visible area, and then re-used depending on which data-source item is currently bound to each one (this changes when you scroll for instance or change the items source). This may cause you problems in the future if you use your current approach, so keep this in mind.
The other thing is that the DataGrid may require more "cycles" of the layout process in order to update its UI. You may simply be calling FindSelectionObject prematurely. You have queued FindSelectionObject right after the invocation in loadTaskList. If the DataGrid needs to perform some actions which are queued on the dispatcher after the items source has changed, these will execute after the invocation in FindSelectionObject.
Try this instead:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
// The items of the grid have changed, NOW we QUEUE the FindSelectionObject
// operation on the dispatcher.
FindSelectionObject(); // <-- (( MOVE IT HERE )) !!
}));
}
EDIT: OK, so if this fails then maybe this will cover the case in which the above solution fails: subscribe to the LoadingRow event of DataGrid and set the appropriate background color if the row is the selected one. So in the cases when new rows are created this event will be called (due to virtualization it is not called per item in items source, but per actual row UI element). In the event args you will have access to the created DataGridRow instance.
I think this issue could be a visual thread synchronization. For this you can create and use a method similar like this:
public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
{
var currentSyncContext = SynchronizationContext.Current;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (_, __) =>
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
currentSyncContext.Send((t) =>
{
IsBusy = true;
BusyText = string.IsNullOrEmpty(text) ? "Espere por favor..." : text;
if (beforeVisualAction != null)
beforeVisualAction();
}, null);
action();
currentSyncContext.Send((t) =>
{
IsBusy = false;
BusyText = "";
if (afterVisualAction != null)
afterVisualAction();
}, null);
};
backgroundWorker.RunWorkerAsync();
}
IsBusy and BusyText are particular properties, that you can remove. The action variable will be the action to do in background (load your items for instance). beforeVisualAction and afterVisualAction are the visual actions you may want to do before and after the background action. Here are any visual update, for instance select your item, change color, set a view model variable that raise a binding update,... (any action that update the view).
Hope this method helps.
Are you maintaining the reference to lastSelectionObject somewhere? You say you're adding new objects, if they are truly new then the reference will be different and the reference comparison happening in IndexOf will not find it.

Save Checked item is listview

I have a listview with a checkbox on each row and I need to make it so that if the user exits the app and later comes back to that liistview the same items remain checked, I have succesfully saved the checked items, but If I try to recheck them in the getview() method the list starts to lag, and random checkboxes start getting checked.
Below is my code that rechecks the boxes
using (VehicleFeaturesDB vfdb = new VehicleFeaturesDB())
{
selectedfeatures = vfdb.GetSelectedFeatures(Selector.vehicleId);
if (listOfSelectedFeatures != null)
{
foreach (Features f in listOfSelectedFeatures)
{
if (feature.FeatureID == f.FeatureID)
{
CheckBox.Checked = true;
}
}
}
}
Tha
If that using block is in GetView(), you're connecting to the database and pulling the list of features every time a new item is displayed. That's why you're seeing the lag.
You're also not setting Checked to false if the feature is not in the list, so when you reuse the convertview the checkbox may already be checked. That's why you're seeing the random boxes checked.
I would probably get the selected features in your adapter's constructor, then reference that list in your GetView(). Something like this should work for you.
public class MyAdapter...
{
private IEnumerable<SelectedFeature> selectedFeatures;
public MyAdapter()
{
...your code...
using (VehicleFeaturesDB vfdb = new VehicleFeaturesDB())
{
selectedfeatures = vfdb.GetSelectedFeatures(Selector.vehicleId);
}
}
public override View GetView(int pos, View convertView, ViewGroup parent)
{
...your code...
CheckBox.Checked = selectedFeatures.Any(sf => sf.FeatureID == feature.FeatureID);
}

Silverlight dataform currentitem issue

I have a page with two controls on it, a datagrid and a dataform.
In the datagrid, I have a list of all the objects of a certain class. When a user selects an item in the datagrid, the dataform is loaded with the selected object.
dataForm.CurrentItem = view.CurrentItem;
view is a PagedCollectionView which contains only the selected item.
My problem is, when setting the dataform's currenitem property, if I use just the PagedCollectionView (view) without .CurrentItem, I lose the validation on the dataform. All the required fields are not seen as required. If I use the pcv.CurrentItem as my dataform's CurrentItem validation works fine, but then another issue arrises.
When I use the PagedCollectionView's current item as the dataform's current item:
A user selects an item in the datagrid and the object is loaded fine in the dataform. If a user changes a certain value in any of the textfields on the dataform and then selects a different item to load the dataform with, the following error is thrown:
"Cannot change currency when an item has validation errors or it is being edited and AutoCommit is false. Set ItemsSource to a ICollectionView to manage currency instead."
I am not using the paging properties of the dataform and I have my own save button on the form.
I would appreciate any help, this is my first silverlight project that I am working on.
Edit- I used dataform.CommitEdit when changing the dataform's currentitem. One thing that this did not resolve is if there is a validation error on the form, the currency error is thrown. Is there anyway to bypass this. AutoEdit is true and AutoCommit is false for the dataform
It's a bit hard to determine exactly what's going on here without a sample, but here's an observation that may help resolve the problem. Try instead to bind the ItemsSource property of both the DataGrid and the DataForm to the collection view, and don't bind the DataForm's CurrentItem property. They're magically kept in sync (the selected item in the DataGrid will set the current item in the DataForm) - this is a feature of the CollectionView. This may or may not solve your problem, but either way it won't hurt :).
Blatant self promotion: this and other features of the CollectionView are covered in my book Pro Business Applications with Silverlight 4 :).
I had this problem a lot of times. And always in case add new item.
After few frustrating days I downloaded source codes of Silverlight toolkit.
(You could find in Programs FIles directory (Mine were is C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Source) )
Compile and reference instead of assembly System.Windows.Controls.Data.DataForm.Toolkit
In Debug mode we see strange behavior in DataForm.cs:
private static void OnCurrentItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataForm dataForm = d as DataForm;
if (dataForm != null && !dataForm.AreHandlersSuspended())
{
if (dataForm._lastItem != null && dataForm.ShouldValidateOnCurrencyChange)
{
dataForm.ValidateItem();
}
if ((!dataForm.AutoCommitPreventsCurrentItemChange && dataForm.IsItemValid) &&
(e.NewValue == null ||
dataForm._collectionView == null ||
dataForm._collectionView.Contains(dataForm.CurrentItem)
))
{
dataForm.SetUpNewCurrentItem();
dataForm.GenerateUI(true /* clearEntityErrors */, true /* swapOldAndNew */);
dataForm.UpdateCurrentItem();
SetAllCanPropertiesAndUpdate(dataForm, false /* onlyUpdateStates */);
dataForm._lastItem = dataForm.CurrentItem;
dataForm.OnCurrentItemChanged(EventArgs.Empty);
}
else
{
dataForm.SetValueNoCallback(e.Property, e.OldValue);
throw new InvalidOperationException(string.Format(Globalization.CultureInfo.InvariantCulture, System.Windows.Controls.Data.DataForm.Toolkit.Resources.DataForm_CannotChangeCurrency, "AutoCommit", "ItemsSource", "ICollectionView"));
}
}
}
dataForm._collectionView.Contains(dataForm.CurrentItem) returns false even the same object exists in dataForm._collectionView
I changed conditional:
if ((!dataForm.AutoCommitPreventsCurrentItemChange && dataForm.IsItemValid) &&
(e.NewValue == null ||
dataForm._collectionView == null ||
dataForm._collectionView.Contains(dataForm.CurrentItem) ||
dataForm.CurrentItem == e.NewValue
))
And DataForm started work fine. Without exception and mistakes.
private void DataForm_EditEnding(object sender, DataFormEditEndingEventArgs e)
{
if (e.EditAction == DataFormEditAction.Commit)
{
...
}
else
{
DataForm1.ValidationSummary.Errors.Clear();
}
}
Check for any validation error when you are binding the current item, if you have any then clear them BindingItem.ValidationErrors.Clear(); then bind the item to dataform.

Categories

Resources