Reorder ListView items: Event and Drag/Drop - c#

My page contains two ListView elements, ListA and ListB. You can copy items (objects) from ListA to ListB by use of simple drag and drop. For that I use the Lists' DragItemsStarting and Drop events. That works just fine. Now you can reorder the items in ListB. All required properties are set (reorder, drag and drop) and this works as well.
But now I want to react on the resorting, but I didn't find an event I could listen to. So I thought it might be possible to use the drag/drop events on the same list to get to know when the user has changed the psoition of an item.
So how can I recognize that the items of ListB have been reorderd?
My code (with the drag and drop approach):
private void ListB_DragItemsStarting(object sender, DragItemsStartingEventArgs e) {
var item = ((FeedItem)e.Items[0]);
e.Data.RequestedOperation = DataPackageOperation.Move;
e.Data.SetDataProvider("FeedItem", request => request.SetData(item));
}
private async void ListB_Drop(object sender, DragEventArgs e) {
DataPackageView view = e.Data.GetView();
if (view.Contains("FeedItem") && view.RequestedOperation == DataPackageOperation.Copy) {
//item from ListA
}
if (view.Contains("FeedItem") && view.RequestedOperation == DataPackageOperation.Move) {
//item from ListB
}
}

Ok, problem solved :)
To use drag and drop on the same UI element (in this case a ListView) you need to disable the CanReorderItems option. Then the code I provided above will work.
ListB.CanReorderItems = false;
But when you wish to keep the option to reorder the items in ListB (actually my original question) you can subscribe to the follwoing event:
view.VectorChanged += viewVectorChanged;
...
private void viewVectorChanged(Windows.Foundation.Collections.IObservableVector<object> sender, Windows.Foundation.Collections.IVectorChangedEventArgs #event) {
}
Since my list is bound to a CollectionViewSource this works fine for me. Otherwise ListView should have a similar event that you could subscribe.

Related

Treeview.Items.Clear() method return null exception (e.NewValue==null) in SelectedItemChanged Event

I am from Iran and I cant speak English very well, sorry.
I made something like OpenFileDialog in WinForms
and work correctly.
After, for better user interface, I tried to make it in WPF.
I use TreeView and other controls for it in both platforms (Winforms and WPF)
in Winforms I could do this correctly usingbelow code:
private void Folder_FileTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
Folder_FileTreeView.Nodes.Clear();//this is necessary to clean first page node, after get new folders
if(e.Node.Text=="Desktop")//also this code is necessary to compare node
{
//Do something
}
}
Also in WPF I can get text of Item by below code:
private void Folder_FileTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue!=null)
{
StackPanel CustomStackPanel = (StackPanel)((TreeViewItem)e.NewValue).Header;
TextBlock textBlock = (TextBlock)CustomStackPanel.Children[1];
nodetext = textBlock.Text;//this line return text of item for compare
}
Folder_FileTreeView.Items.Clear();
}
If I don't use Folder_FileTreeView.Items.Clear() the above code return folders without clearing first page, but if I do use Folder_FileTreeView.Items.Clear() e.NewValue returns null.
Please help me to use together these codes: Folder_FileTreeView.Items.Clear();(or clear first page) and get text of selecteditem by user without return null
Thanks A lot
e.NewItem will be null if the TreeView used to have an item selected but now does not. When you clear the items, you are removing any selection, this of course changes the selection and raises the SelectedItemChanged event with null as the new selection- since there are no possible items that could be selected.
If you want to replace the items in the list with new items after the user makes a selection, the selected item will be null while that change is happening. You need to do the following:
Handle the SelectedItemChanged event and remember the new selected item in a variable.
For example, if they click on the item for "Desktop" set a variable (e.g. Path) to the path for the user's desktop (e.g. C:\Users\UserName\Desktop).
Clear the list of folders in the TreeView. This will trigger SelectedItemChanged again, but you want to ignore it this time because e.NewItem == null.
Read all the folders in Path and make new items for each of those folders.
The way was found by below code
private void Folder_FileTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Folder_FileTreeView.SelectedItemChanged -= Folder_FileTreeView_SelectedItemChanged;
if (e.NewValue!=null)
{
StackPanel CustomStackPanel = (StackPanel)((TreeViewItem)e.NewValue).Header;
TextBlock textBlock = (TextBlock)CustomStackPanel.Children[1];
nodetext = textBlock.Text;//this line return text of item for compare
}
Folder_FileTreeView.Items.Clear();
Folder_FileTreeView.SelectedItemChanged += Folder_FileTreeView_SelectedItemChanged;
}
thank very much for every one helped me

Filter items in a ListView in real time

My program generates ListView full of information. I type into a text box a name that might match one of the item names in the ListView. I want this typed name to weed out the names from the ListView that don't match.
For example, if I type in "abc", names like "uvw" and "xyz" wouldn't show up anymore, but "abc" and "abcde" would still show up in the list view.
The end goal is to be able to check the checkboxes next to the names I want, and search for more names, eventually selecting several, without resetting the checkboxes.
Right now I click a button and the ListView is populated:
private void button1_Click(object sender, EventArgs e)
{
List<string> myList = getList();
foreach(string s in myList)
{
listView1.Items.Add(s);
}
}
getList() just returns a List<string> of all the names I want.
I can't figure out how to make the ListView update in real time when I type in my text box. I'm able to update it with a button click via repopulating the ListView based on looping through the List, and checking each name, but that's not what I want. It also doesn't retain checked check boxes, as it's a newly generated list each time.
I read about a "text change listener", but I'm not sure that's what I should be using here...
With filtering you need some way of remembering which ListViewItems are selected, so instead of inserting all your ListViewItems into your listview you want to instantiate them in a master list. Then attach a TextChanged event handler to your text box and when the text changes you display the items.
List<ListViewItem> masterlist;
public Form1()
{
InitializeComponent();
masterlist = new List<ListViewItem>();
}
private void button1_Click(object sender, EventArgs e)
{
// Populate the masterlist
masterlist.Clear();
foreach(string s in getList())
{
masterlist.Items.Add(new ListViewItem(s));
}
// Display the items in the listview
DisplayItems();
}
private void DisplayItems()
{
listView1.Items.Clear();
// This filters and adds your filtered items to listView1
foreach(ListViewItem item in masterlist.Where(lvi => lvi.Text.ToLower().Contains(textBox1.Text.ToLower().Trim())))
{
listView1.Items.Add(item);
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
// Re-display the items when the filter changes
DisplayItems();
}
As you're dealing directly with ListViewItems in your masterlist they will retain their checked state when swapped in and out of listView1.
I have assumed that your filter textbox is called textBox1.
If you want to go for a full C# solution (rather than using any Javascript), as much as it pains me, I would suggest using an UpdatePanel.
Put your ListBox inside they the <ContentTemplate> section of the <UpdatePanel> then add an <asp:AsyncPostBackTrigger> with the ControlID set to that of your textbox. Make sure that the UpdateMode property of the UpdatePanel is set to "Conditional".
On your TextBox you will also have to set the AutoPostBack property to true. On the TextBox itself you will have to create a TextChanged event handler, then in your code behind (.cs file) you will have the logic for your TextChanged handler which will filter the list then set the new value for your ListBox.
UpdatePanels are ok for simply scenarios, but you can very easily get yourself into trouble by mis-using them.

c# confirm each combobox has a unique value selected

I have a winform with a group of comboboxes, all of the comboboxes with the same list items in them.
I need a way to confirm that when the user is done selecting a value in each box that they only selected each list value once.
Ex:
cbox1 cbox2 cbox 3
Item A Item B Item A (this needs to flag an error since Item A is already selected in cbox1)
I was thinking trying to use the selectedvaluecommited action (as after i populate the list I change the selected index to -1 so they all show "empty" to start) but the loop to make it work seems to be eluding me.
background: this is choosing fields to build a spreadsheet and the user needs to choose the field order.
You can do it like this (quick and dirty):
Add SelectedIndexChanged handler for all three comboboxes (in Form_Load in example)
comboBox1.SelectedIndexChanged += CheckComboBoxes;
comboBox2.SelectedIndexChanged += CheckComboBoxes;
comboBox3.SelectedIndexChanged += CheckComboBoxes;
in CheckComboBoxes method do your checking:
private void CheckComboBoxes(object sender, EventArgs e)
{
if (comboBox1.SelectedIndex == comboBox2.SelectedIndex ||
comboBox1.SelectedIndex == comboBox3.SelectedIndex ||
comboBox2.SelectedIndex == comboBox3.SelectedIndex)
MessageBox.Show("comboboxes are not unique");
}
EDIT:
this is approach when having n comboboxes. Put all items into list, select distinct values and compare that distinct count with items count... Something like this:
private void CheckComboBoxes(object sender, EventArgs e)
{
List<string> comboValues = new List<string>();
foreach (Control c in this.Controls)
{
if (c is ComboBox && !string.IsNullOrEmpty((c as ComboBox).SelectedItem.ToString()))
comboValues.Add((c as ComboBox).SelectedItem.ToString());
}
if (comboValues.Distinct().ToList().Count < comboValues.Count)
MessageBox.Show("not all combos are unique");
}
Here's an approach you can take.
To make the affected comboboxes easy to distinguish, put them all in a GroupBox container.
Write a validation method for your group box.
Subscribe to the group box Validating event by attaching it to your validation method.
In your validation method, loop through all the ComboBox controls in the group box and check if there are any duplicates, and issue an error if so.
For example, assuming the group box is called groupBox1:
private void GroupBox1_Validating(object sender, CancelEventArgs e)
{
base.OnValidating(e);
var selectedIndices = groupBox1.Controls.OfType<ComboBox>().Select(item => item.SelectedIndex);
var anyDuplicates = selectedIndices.GroupBy(x => x).Any(x => x.Count() > 1);
if (!anyDuplicates)
return;
MessageBox.Show("There are duplicates!");
e.Cancel = true;
}
And subscribe to the group box Validating event in the Form1 constructor:
public Form1()
{
InitializeComponent();
groupBox1.Validating += GroupBox1_Validating;
}
Sometimes when validating like this, you need to prevent the validation logic from executing if the user clicks the Cancel button. You're supposed to be able to set the CausesValidation property of the Cancel button to false to prevent this, but I find that it doesn't work for me.
Instead, I just use a bool cancelling field which I set to true in the Cancel button handler:
private void cancelButton_Click(object sender, EventArgs e)
{
cancelling = true;
this.Close();
}
bool cancelling;
And then add the following to the start of GroupBox1_Validating():
if (cancelling)
return;
If it is possible to have different UI design then my suggestion goes as under:
Alternative UI Design - A
Create One ListBox ListFieldsOriginal and populate
Create Second ListBox ListUserSelection, keep it empty initially
Provide buttons as under:
Button '>' means add currently selected item from ListFieldsOrginial to ListUserSelection at end; and remove that item from ListFieldsOriginal
Button '<' means remove currenly selected item from lstUserSelection; and add that item back to ListFieldsOriginal (of course at end)
NOTE: If adding item back to ListFieldsOriginal is your requirement then extra coding is required to find its appropriate index in the ListFieldsOriginal.
Alternative UI Design - B
Create One CheckedListBox ListFieldsOriginal and populate
Create one ListBox ListUserSelection, keep it empty initially
Define ItemCheck event handler for ListFieldsOriginal to add/remove items to/from ListUserSelected.
if (e.CurrentValue==CheckState.Unchecked)
{
string item = ListFieldsOriginal.Items[item];
ListUserSelection.Items.Add(item);
}
else
{
string item = ListFieldsOriginal.Items[item];
ListUserSelection.Items.Remove(item);
}

Datagrid is not properly refreshing on row delete

I am working with a datagrid in WPF which is bound to a collectionviewsource. The viewsource is bound to an observable collection named Rows.
The datagrid has add and delete functions which function properly except on small problem.
Here are images:
The datagrid has more data than this. Each test starts off with two sequences (the two rows you see belong to a single test) and they are grouped and sorted by a unique ID.
I have clicked the red "X" to delete the row. I will now click the "Add" button located at the top-left of the image.
The data is still there.
These are my add and delete functions:
private void Add(object sender, ExecutedRoutedEventArgs e)
{
var testRun = e.Parameter as TestRun;
if (testRun != null)
{
var numberOfRows = testRun.Property.GetValue("numberOfRows").ToNullable<int>().GetValueOrDefault(2);
numberOfRows++;
testRun.Property.SetValue("numberOfRows", numberOfRows.ToString());
this.Rows.Add(new ESCHandle(testRun, numberOfRows));
}
}
private void Delete(object sender, ExecutedRoutedEventArgs e)
{
var esc = e.Parameter as ESCHandle;
if (esc != null)
{
this.Rows.Remove(esc);
var numberOfRows = esc.TestRun.Property.GetValue("numberOfRows").ToNullable<int>().GetValueOrDefault(2);
numberOfRows--;
esc.TestRun.Property.SetValue("numberOfRows", numberOfRows.ToString());
}
}
The ESC object is properly removed from the observablecollection on Delete. But on when I add another ESC object/row to the colleciton and datagrid, the data is somehow copied to the new object.
You may forget to call a refresh method on datagrid to update its visual elements, such as the rows.
Datagrid.Items.Refresh(), as described here:
http://programmer.wrighton.org/2009/01/wpf-datagrid-items-refresh.html
This problem may be caused because PropertyChange is not raised properly.

Can't remove item from ObservableCollection through ContextMenu

I have a program with a class called MyClass and Location. MyClass contains an ObservableCollection of Location items and Location contains a string property called Name. In MainPage.xaml I have a LongListSelector (with a ContextMenu for each item) populated with grids representing a Location.
When I click the 'remove' menu item from the context control, it will usually remove the underlying Location object and update the view. After a few cycles of populating the LongListSelector and removing all its items, some new items that are added can't be removed anymore.
Here's an example of what I mean: The LLS originally contains 2 items. Then I delete those 2 items and add 3 more. However, I can only remove the third one, in this case, but not the first 2.
Here's the ContextMenu MenuItem click event from MainPage.xaml.cs:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var selectedItem = (sender as MenuItem).DataContext as Location;
for (int i = 0; i < MyClass.Locations.Count; i++)
{
if (MyClass.Locations[i].Name == selectedItem.Name)
{
MyClass.Locations.Remove(MyClass.Locations[i]);
break;
}
}
}
Prior to using a for loop, I used this LINQ code and still had the same problem:
var toRemove = MyClass.Locations.Where(x => x.Name == selectedItem.Name).SingleOrDefault();
MyClass.Locations.Remove(toRemove);
Any suggestions to fix this problem?
I suggest you to use a ListBox instead of LLS - if you are not using grouping option. It works much better and causes less problems.
By the way I've also encountered some problems with this Control - maybe similar to yours. Weird is also that LLS.UpdateLayout() doesn't work while in ListBox works perfect.

Categories

Resources