I'm using a Listbox wrapped inside a ListBoxDragDropTarget (from the Silverlight toolkit).
This ListBox ca be manually reordered by the user. However the last item must always be located at the bottom of the ListBox, and can't be moved at all. I found a way to cancel this last item moves, but if I drag and drop another item below this last item, it gets moved.
I can find the index of the dragged item using this code in the Drop event of the ListBox:
object data = e.Data.GetData(e.Data.GetFormats()[0]);
ItemDragEventArgs dragEventArgs = data as ItemDragEventArgs;
SelectionCollection selectionCollection = dragEventArgs.Data as SelectionCollection;
if (selectionCollection != null)
{
MyClass cw = selectionCollection[0].Item as MyClass;
int idx = selectionCollection[0].Index.Value;
}
However this only gives me the index before the drag operation.
My question is the following: Is there a way to know the new index of the dropped item ? This way, if the index = last position of the list, I can move it to the forelast position.
Thanks in advance !
Ok I found a way to do this. I bound the ItemDragCompleted event of the ListBoxDragDropTarget, and did the following:
private void dropTarget1_ItemDragCompleted(object sender, ItemDragEventArgs e)
{
var tmp = (e.DragSource as ListBox).ItemsSource.Cast<MyClass>().ToList();
SelectionCollection selectionCollection = e.Data as SelectionCollection;
if (selectionCollection != null)
{
MyClass cw = selectionCollection[0].Item as MyClass;
int idx = tmp.IndexOf(cw);
if (idx == tmp.Count - 1)
{
tmp.Remove(cw);
tmp.Insert(tmp.Count - 1, cw);
MyListBox.ItemsSource = new ObservableCollection<MyClass>(tmp);
}
}
}
As the DragSource represents the Listbox, with the new "arrangement" of items, I can therefore check if the item is now located at the end, and move it in this case.
The only problem left is that it causes a flicker on the screen, due to the item being dropped and then moved.
I'm still open to any other (best) suggestion.
I have this exact same problem. I think it may be possible to loop through the rectangles of the items in the ListBox and see if var point = args.GetPosition(myListBox); is within them. But I am hoping for an easier way...
Edit: The problem with this though, is you don't get the gravity effect that is already built in to silverlight where only half of the above and half of the below rectangle are used to drop it into the list.
Related
I have a combobox that is created with data from a dataset
foreach(var item in ds.MiDESValues)
{
string comboboxtext = ds.MiDESValues.Rows[k][1].ToString();
sFactorCB.Items.Add(comboboxtext);
k++;
}
On a selectionchanged event it will populate a listbox with that selection
private void sFactors_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
string add = sFactorCB.SelectedValue.ToString();
var svalue = ds.MiDESValues.Rows[0][2].ToString();
int Svalue = int.Parse(svalue);
SValue.Add(svalue);
SelectionListBox.Items.Add(add);
SelectionBox.Add(add);
// when a new item is added to Selection list box, select it and show it
// this will keep the last item highlighted and as the list grows beyond
// the view of the list box, the last item will always be shown
SelectionListBox.SelectedIndex = SelectionListBox.Items.Count - 1;
SelectionListBox.ScrollIntoView(SelectionListBox.SelectedItem);
}
That list box then used to populate a listbox used on the next page. If I navigate to the next page and then navigate back, the combobox is still showing the last selection I made therefor the listbox is being populated with that value.
I have tried setting the selectedindex of the combobox to sFactorCB.SelectedIndex = -1;, at the end of the sFactors_SelectionChanged event but i get System.NullReferenceException. How can I get the combobox to reset back to a non-selected item state? Thanks
Actually, you are doing it correctly. To clear the selection either set the SelectedIndex to -1 or the SelectedItem to null.
The problem is that once you do that your sFactors_SelectionChanged gets called again and since there is no current selection, the SelectedValue property is null thus causing the following to fail:
string add = sFactorCB.SelectedValue.ToString();
How to solve this sort of depends on your intended results when nothing is selected. The simplest thing to do would be to just check for nothing selected at the start of the handler and simply return.
private void sFactors_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sFactorCB.SelectedIndex == -1 || sFactorCB.SelectedValue == null)
return;
string add = sFactorCB.SelectedValue.ToString();
...
When my datagrid loads up I am able to get the focus of the first row by providing selected index=0 in the xaml but when I perform searching the focus gets lost so I want focus to be retain at the first row no matter I do sorting and searching on the datagrid.
Here is my code that searches particular thing in the datagrid.
private void TextBox_TextChanged(object sender, RoutedEventArgs e)
{
TextBox STB = (TextBox)sender;
this.SearchValue = STB.Text;
//ContentPresenter CP = (ContentPresenter)STB.TemplatedParent;
//DataGridColumnHeader DGCH = (DataGridColumnHeader)CP.TemplatedParent;
//DataGridColumn DGC = DGCH.Column;
//this.ColumnName = DGC.Header.ToString();
this.Datalist.Filter = this.CustomeFilter;
DataGrid dataGrid = this as DataGrid;
dataGrid.CurrentCell = new DataGridCellInfo(
dataGrid.Items[0], dataGrid.Columns[0]);
dataGrid.BeginEdit();
}
In above code I am trying to get the focus of the current cell but all in vain.
private bool CustomeFilter(object item)
{
SymbolData ltpObj = item as SymbolData;
//WpfApplication1.Model.LtpMessage ltpObj = item as WpfApplication1.Model.LtpMessage;
string values = (string)ltpObj.Symbol.ToString();
values = values.ToUpper();
//return values.StartsWith(this.SearchValue.ToString().ToUpper());
if (values.StartsWith(this.SearchValue.ToString().ToUpper()))
{ return true; }
else
return false;
}
You should read this:
How to programmatically select and focus a row or cell in a DataGrid in WPF: https://blog.magnusmontin.net/2013/11/08/how-to-programmatically-select-and-focus-a-row-or-cell-in-a-datagrid-in-wpf/
You can select and focus a row or cell of a DataGrid programmatically and get the same behaviour as when using the mouse by accessing the visual user interface elements of the DataGrid control and calling the UIElement.Focus() method on a particular DataGridCell object as described in the blog post above. There are code samples included.
You cannot simply set the SelectedItem or SelectedIndex property of the DataGrid to focus the row or cell though.
I have Datagrid and Text Box in my Form. Datagrid is showing me existing items in my stock. I use Text Box to search and set focus to that row which is matching with my Text Box. Now it is working fine when VirtualizingStackPanel.IsVirtualizing="false" but it is very slow and getting a lot RAM resource.
Here is my code for this.
public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
if (null == itemsSource) yield return null;
foreach (var item in itemsSource)
{
var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
if (null != row) yield return row;
}
}
private void SearchBoxDataGrid_TextChanged(object sender, TextChangedEventArgs e)
{
var row = GetDataGridRows(AssortDataGrid);
/// go through each row in the datagrid
foreach (Microsoft.Windows.Controls.DataGridRow r in row)
{
DataRowView rv = (DataRowView)r.Item;
// Get the state of what's in column 1 of the current row (in my case a string)
string t = rv.Row["Ассортимент"].ToString().ToLower();
if (t.StartsWith(SearchBoxDataGrid.Text.ToLower()))
{
AssortDataGrid.SelectedIndex = r.GetIndex();
AssortDataGrid.ScrollIntoView(AssortDataGrid.SelectedItem);
break;
}
}
}
What I want is to make it VirtualizingStackPanel.IsVirtualizing="true" but in this case my method is not working. I know why it is not working, my code will work only for showing part of Datagrid.
What do you recommend? How to fix this issue? Any idea will be appreciated. If you give any working code it will be fantastic. I hope I could explain my problem.
Virtualization means that WPF will reuse the UI components, and simply replace the DataContext behind the components.
For example, if your Grid has 1000 items and only 10 are visible, it will only render around 14 UI items (extra items for scroll buffer), and scrolling simply replaces the DataContext behind these UI items instead of creating new UI elements for every item. If you didn't use Virtualization, it would create all 1000 UI items.
For your Search to work with Virutalization, you need to loop through the DataContext (DataGrid.Items) instead of through the UI components. This can either be done in the code-behind, or if you're using MVVM you can handle the SeachCommand in your ViewModel.
I did a little coding and make it work. If anyone needs it in future please, use it.
Firstly I am creating List of Products
List<string> ProductList;
Then on Load Method I list all my products to my Product List.
SqlCommand commProc2 = new SqlCommand("SELECT dbo.fGetProductNameFromId(ProductID) as ProductName from Assortment order by ProductName desc", MainWindow.conn);
string str2;
SqlDataReader dr2 = commProc2.ExecuteReader();
ProductList = new List<string>();
try
{
if (dr2.HasRows)
{
while (dr2.Read())
{
str2 = (string)dr2["ProductName"];
ProductList.Insert(0, str2.ToLower ());
}
}
}
catch (Exception ex)
{
MessageBox.Show("An error occured while trying to fetch data\n" + ex.Message);
}
dr2.Close();
dr2.Dispose();
After that I did some changes in SearchBoxDataGrid_TextChanged
private void SearchBoxDataGrid_TextChanged(object sender, TextChangedEventArgs e)
{
int pos = 0;
string typedString = SearchBoxDataGrid.Text.ToLower();
foreach (string item in ProductList)
{
if (!string.IsNullOrEmpty(SearchBoxDataGrid.Text))
{
if (item.StartsWith(typedString))
{
pos = ProductList.IndexOf(item);
AssortDataGrid.SelectedIndex = pos;
AssortDataGrid.ScrollIntoView(AssortDataGrid.SelectedItem);
break;
}
}
}
}
Now it works when VirtualizingStackPanel.IsVirtualizing="true".
That is all.
I have a WPF Datagrid and I'm implementing drag and drop functionality.
The datagrid has a list of "files" and the user can drag them and copy the file to the desktop.
This is done like this:
string[] files = new String[myDataGrid.SelectedItems.Count];
int ix = 0;
foreach (object nextSel in myDataGrid.SelectedItems)
{
files[ix] = ((Song)nextSel).FileLocation;
++ix;
}
string dataFormat = DataFormats.FileDrop;
DataObject dataObject = new DataObject(dataFormat, files);
DragDrop.DoDragDrop(this.myDataGrid, dataObject, DragDropEffects.Copy);
I have two questions:
1. When I want to drag multiple items- this is a problem because after I select a couple and start clicking on one to start dragging- only that gets selected and the other items get deselected. I tried the solution that is given here but for some reason it doesn't work.
2. I want to remove the dragged item from the datagrid after it is copied. The problem is that I don't know how to check if the file was copied or whether the user just dragged it on the screen without copying it.
I hope you can help me solve these problems.
Thanks!
I think this i what you are looking for:
add this code to the DataGrid__PreviewMouseLeftButtonDown event handler:
private void DataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.startingPosition = e.GetPosition(null);
DependencyObject dep = (DependencyObject)e.OriginalSource;
// iteratively traverse the visual tree until get a row or null
while ((dep != null) && !(dep is DataGridRow))
{
dep = VisualTreeHelper.GetParent(dep);
}
//if this is a row (item)
if (dep is DataGridRow)
{
//if the pointed item is already selected do not reselect it, so the previous multi-selection will remain
if (songListDB.SelectedItems.Contains((dep as DataGridRow).Item))
{
// now the drag will drag all selected files
e.Handled = true;
}
}
}
and now the draging won't change you selection.
Have a good luck!
I used that article to write my answer
Improved on finding the row.
Also selecting the clicked row when not dragging is added.
This now behaves exactly as other Microsoft selectors (eg Outlook)
public TreeDataGrid()
{
Loaded += TreeDataGrid_Loaded;
LoadingRow += new EventHandler<DataGridRowEventArgs>(TreeDataGrid_LoadingRow);
}
#region MultiSelect Drag
object toSelectItemOnMouseLeftButtonUp;
void TreeDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(Row_PreviewMouseLeftButtonDown);
e.Row.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(Row_PreviewMouseLeftButtonUp);
}
void Row_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
toSelectItemOnMouseLeftButtonUp = null; // always clear selecteditem
if (SelectedItems.Contains(row.Item)) //if the pointed item is already selected do not reselect it, so the previous multi-selection will remain
{
e.Handled = true; // this prevents the multiselection from disappearing, BUT datagridcell still gets the event and sets DataGrid's private member _selectionAnchor
toSelectItemOnMouseLeftButtonUp = row.Item; // store our item to select on MouseLeftButtonUp
}
}
void Row_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
if (row.Item == toSelectItemOnMouseLeftButtonUp) // check if it's set and concerning the same row
{
if (SelectedItem == toSelectItemOnMouseLeftButtonUp) SelectedItem = null; // if the item is already selected whe need to trigger a change
SelectedItem = toSelectItemOnMouseLeftButtonUp; // this will clear the multi selection, and only select the item we pressed down on
typeof(DataGrid).GetField("_selectionAnchor", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(this, new DataGridCellInfo(row.Item, ColumnFromDisplayIndex(0))); // we need to set this anchor for when we select by pressing shift key
toSelectItemOnMouseLeftButtonUp = null; // handled
}
}
#endregion
This article shows how to implement a copy operation on a drop event. I'd like to do the same but I want my dropped item to appear in the collection according to where it was placed on the UI. So I need the StartIndex much like on a NotifyCollectionChangedEventArgs when an ObservableCollection changes. In the article you'll see that eventually you get a SelectionCollection object whose items have an Index property. But unfortunately this is the index of the source collection (where it was picked) and not the destination collection (where it was dropped).
Ok, this is quite ugly, but I didn't find another way, not by myself and also not by searching the net for answers. Must have been another deadline at Microsoft that prevented the rather obvious functionality to be included...
Basically the method below does everything manually, getting the drop location and checking it for listbox items to use as index references.
private void ListBoxDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
// only valid for copying
if (e.Effects.HasFlag(DragDropEffects.Copy))
{
SelectionCollection selections = ((ItemDragEventArgs)e.Data.GetData("System.Windows.Controls.ItemDragEventArgs")).Data as SelectionCollection;
int? index = null;
if (selections != null)
{
Point p1 = e.GetPosition(this.LayoutRoot); // get drop position relative to layout root
var elements = VisualTreeHelper.FindElementsInHostCoordinates(p1, this.LayoutRoot); // get ui elements at drop location
foreach (var dataItem in this.lbxConfiguration.Items) // iteration over data items
{
// get listbox item from data item
ListBoxItem lbxItem = this.lbxConfiguration.ItemContainerGenerator.ContainerFromItem(dataItem) as ListBoxItem;
// find listbox item that contains drop location
if (elements.Contains(lbxItem))
{
Point p2 = e.GetPosition(lbxItem); // get drop position relative to listbox item
index = this.lbxConfiguration.Items.IndexOf(dataItem); // new item will be inserted immediately before listbox item
if (p2.Y > lbxItem.ActualHeight / 2)
index += 1; // new item will be inserted after listbox item (drop location was in bottom half of listbox item)
break;
}
}
if (index != null)
{
foreach (var selection in selections)
{
// adding a new item to the listbox - adjust this to your model
(lbxConfiguration.ItemsSource as IList<ViewItem>).Insert((int)index, (selection.Item as ViewItem).Clone());
}
}
}
}
}