I'm seeing some odd behavior when implementing a drag and drop feature in a bound RadListView. The events trigger properly, and the item being moved does in fact get moved, but there seems to be a very narrow drop range, and I'm hoping there's a way to fix that. Basically, if I drop the item above the drop line, all is well, but if it's below the drop line, it gets added to the list under the next item.
It's like this:
My Task List
What should happen:
Drag and prepare to drop above the line
Drop above the line, and the item is where it should be
What happens if you're off by a millimeter on the drop:
Drag and prepare to drop below the line
Drop below the line, and the item is dropped on the next index
Here's the event code, if that's helpful:
private void lvProjectTasks_PreviewDragDrop(object sender, RadDropEventArgs e)
{
BaseListViewVisualItem draggedItem = e.DragInstance as BaseListViewVisualItem;
SimpleListViewVisualItem itemElement = e.HitTarget as SimpleListViewVisualItem;
if (itemElement == null)
return;
e.Handled = true;
RadListView lv = ((ListViewDragDropService)sender).DraggedItem.ListView;
int index = lv.Items.IndexOf(itemElement.Data);
dynamic newTask;
if (draggedItem.Data.DataBoundItem.GetType() == typeof(X))
newTask = draggedItem.Data.DataBoundItem as X;
else
newTask = draggedItem.Data.DataBoundItem as Y;
if (index > -1)
{
List<dynamic> items = new(lv.Items.Count);
foreach (ListViewDataItem item in lv.Items)
{
dynamic task;
if (item.GetType() == typeof(X))
task = item.DataBoundItem as X;
else
task = item.DataBoundItem as Y;
task.SortOrder = (short)lv.Items.IndexOf(item);
items.Add(task);
}
items.Remove(draggedItem.Data.DataBoundItem);
newTask.SortOrder = (short)lv.Items.IndexOf(draggedItem.Data.DataBoundItem);
items.Insert(index, newTask);
lv.DataSource = items;
}
}
Is there any way to make the drop zone behave the same above and below the line, or is the drop actually acting on the item being hovered over pre-drop, regardless of the line's appearance?
Related
I'm having a bit of a weird problem where I am trying to update the checked status of a checkbox contained inside a ListBoxItem, I am unable to get a ListBoxItem after the 7th item I've tried several methods to get this to work, as seen in the below method. itemIndex each time does have a positive value (so the I know the item is being found), but why It cant get the listboxitem I do not know
private IEnumerable<CheckBox> GetListBoxItemCheckBoxes(object item)
{
var itemIndex = LstItems.Items.IndexOf(item);
var selectedListBoxItem = LstItems.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
var selectedListBoxItemCheckBoxes = selectedListBoxItem?.FindVisualChildrenOfType<CheckBox>();
if (selectedListBoxItemCheckBoxes == null)
{
selectedListBoxItem = LstItems.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
selectedListBoxItemCheckBoxes = selectedListBoxItem?.FindVisualChildrenOfType<CheckBox>();
if (selectedListBoxItemCheckBoxes == null)
{
itemIndex = LstItems.ItemContainerGenerator.Items.IndexOf(item);
selectedListBoxItem = LstItems.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
selectedListBoxItemCheckBoxes = selectedListBoxItem?.FindVisualChildrenOfType<CheckBox>();
}
}
return selectedListBoxItemCheckBoxes;
}
I think that this may be to do with the timing of when I am trying to set the checkbox states as I am trying to do it after I have added that item? I've read a few questions relating to this area on SO but so far none of have been able to help me with my problem, I thought this answer might be close.. but It gave me the same result: https://stackoverflow.com/a/23501378/1800140
The items are not bound, they are added using ListBox.Items.Add (not too familiar with binding).
I am also doing this in a background thread as I need to refresh the contents of my listbox regularly and need to make an api call to do so.
The method that is being used for updating the contents of my listbox is as below. SetItemChecked calls the first method to get the checkbox, however this starts to return null after the 7th item
public void ResetAndAddItems<T>(IEnumerable<T> items, string displayByProperty = "",
Func<T, string> displayByFunc = null,
Dictionary<string, bool> checkedStates = null,
Func<ListItem<T>, Dictionary<string, bool>, bool> checkedStatesKeyFunc = null)
{
Dispatcher.Invoke(() =>
{
LstItems.Items.Clear();
});
var listedItems = items?.ToList();
if (listedItems == null || !listedItems.Any())
{
return;
}
foreach (var item in listedItems)
{
var listItem = new ListItem<T>
{
DisplayByProperty = displayByProperty,
DisplayByFunc = displayByFunc,
Item = item
};
Dispatcher.Invoke(() =>
{
LstItems.Items.Add(listItem);
if (checkedStates != null && checkedStatesKeyFunc != null)
{
SetItemChecked(item, checkedStatesKeyFunc(item as ListItem<T>, checkedStates));
}
});
}
}
UI Virtualization is set to True by default on ListBoxes. And if UI Virtualization is enabled, containers will only be created for the visible items. Try setting this attached property:
VirtualizingStackPanel.IsVirtualizing="False"
To fix this issue, I had to result to binding.
The issue is that because only 7 items are display on the listview at once only 7 items are drawn, to get around this I can scroll to the next item after the 7th item and so on however this was noticeably slower.
I have a form that contains a TableLayoutPanel with various controls and labels in it. One of them is a custom control that inherits from ComboBox that has extra auto-complete behavior (auto-completes on any text rather than just left to right). I didn't write the code for this control, so I'm not super familiar with how it works, but essentially upon clicking on the Combobox, it adds a ListBox below the ComboBox, within the same Panel of the TableLayoutPanel, that covers the normal drop down.
Unfortunately, the TableLayoutPanel prevents the ListBox from being fully visible when added, and only one item is shown. The goal is to get it to look like a normal ComboBox which would drop down to cover any controls below it.
Is there any way to allow a control that is in a TableLayoutPanel to overlap the TableLayoutPanel to get this to work as I want? I want to avoid any controls moving around due to the TableLayoutPanel growing to accommodate the ListBox.
Relevant code from the control:
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.ToLower().Contains(SearchText.ToLower()))
{
listBoxChild.Items.Add(Item);
listBoxChild.SelectedIndex = 0;
}
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Anchor = ((System.Windows.Forms.AnchorStyles)
((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
TheControlToMove.BringToFront();
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
Images:
Desired behavior
Current behavior
Going on the suggestion from TaW, I came up with a tentative solution. This form isn't re-sizable but does auto-size so that it looks ok if the user changes their DPI in Windows.
To resolve this, I moved the control out of the TableLayoutPanel to an arbitrary position in the Parent of the TableLayoutPanel. On form loading, I summed the coordinates of the TableLayoutPanel and an empty panel in the cell that I wanted the control to be located on top of. This worked for my needs but it feels like a kludge.
The better solution is probably to use Control.PointToScreen and Control.PointToClient methods, however I wasn't able to get these methods to give me the correct coordinates.
I have a ListBox in a Windows Phone app. In a button action I need to set a transformation and name on every ListBoxItem in the ListBox called lb.
My datasource is
var items = new ObservableCollection<string>();
for (int i = 0; i < 10; ++i)
{
items.Add("Item " + i);
}
lb.ItemsSource = items;
I have a code to add a RenderTransform to each ListBoxItem in the ListBox
for (int i = 0; i < items.Count;++i )
{
var item = this.lb.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
item.RenderTransform = new CompositeTransform();
item.Name = i.ToString() //needed for storybord
//another stuff
}
and it works ok. The problem is that I first need to insert and item to the list. When I call items.Insert(index,"test") before the for loop I get an exception that the item is null when i==index. It does not matter when I insert the new item, I always get null for that item.
What am I doing wrong? Or is there an event of the ListBox I need to wait for when I insert the new item before trying to acces the ListBoxItem?
Edit: I extracted the code and put it into a solution: https://dl.dropboxusercontent.com/u/73642/PhoneApp2.zip. I first insert a fake item to the new solution, the fade it away and move the original item to that position using an animation.
Right after item added there is not container generated because of asynchronous nature of UI subsystem. Try subscribing on the ItemsChanged (or StatusChanged, sorry i don't remember) and get item when event is fired with proper event args.
Waiting for the Dispatcher to finish doing what its doing such as (updating the UI because of a new item being added)
this.Dispatcher.BeginInvoke(() =>
{
//Code Here
});
If you ever manipulate the UI such as adding an item to a listbox without the UI getting updated, you will not be able to run code targeting the UI.
Edit: Here is the code for your project to get working
private void Button_Click(object sender, RoutedEventArgs e)
{
start = Int32.Parse(from.Text);
end = Int32.Parse(to.Text);
fake = items[start];
//items.Insert(end, fake);
this.Dispatcher.BeginInvoke(() =>
{
for (int i = 0; i < items.Count; ++i)
{
var item = this.lb.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
item.Name = i.ToString();
}
(this.lb.ItemContainerGenerator.ContainerFromIndex(end) as ListBoxItem).RenderTransform = new CompositeTransform();
(this.lb.ItemContainerGenerator.ContainerFromIndex(end) as ListBoxItem).Name = "listBoxItem1";
(this.lb.ItemContainerGenerator.ContainerFromIndex(start) as ListBoxItem).Name = "listBoxItem";
sbListBox.Begin();
});
}
In the constructor i did:
if (listBox1.Items != null)
{
listBox1.Focus();
}
But when im running the program i cant move with the keyboards up down in listBox since the focus is on a button somewhere else in the Form. I need to click with the mouse on the listBox to get the focus.
Another problem i want that when the user add a new item to the listBox the focus will be automatic on the last added item. For this problem this is the code where im adding a new item to the listBox:
private void KeysValuesUpdate()
{
using (var w = new StreamWriter(keywords_path_file))
{
crawlLocaly1 = new CrawlLocaly();
crawlLocaly1.StartPosition = FormStartPosition.CenterParent;
DialogResult dr = crawlLocaly1.ShowDialog(this);
if (dr == DialogResult.OK)
{
if (LocalyKeyWords.ContainsKey(mainUrl))
{
LocalyKeyWords[mainUrl].Clear();
LocalyKeyWords[mainUrl].Add(crawlLocaly1.getText());
}
else
{
LocalyKeyWords[mainUrl] = new List<string>();
LocalyKeyWords[mainUrl].Add(crawlLocaly1.getText());
}
Write(w);
ClearListBox();
}
if (dr == DialogResult.Cancel)
{
Write(w);
}
}
}
private void ClearListBox()
{
data.Clear();
listBox1.DataSource = null;
string sb;
foreach (KeyValuePair<string, List<string>> kvp in LocalyKeyWords)
{
for (int i = 0; i < kvp.Value.Count(); i++)
{
sb = "Url: " + kvp.Key + " --- " + "Local KeyWord: " + kvp.Value[i] + Environment.NewLine;
data.Add(sb.ToString());
}
}
listBox1.DataSource = data;
}
The question is why i cant set the focus in any of the cases on the listBox items ?
In the first case in the constructor the focus i want it to be on the last item in the list and also each time im adding a new item so the focus will be on the last added item.
Most likely, the item is being selected, you just can't tell because a different control has the focus. There are a couple of different ways that you can solve this, depending on the design of your application.
For the first part of the question, you should set the Focus in the Page/Form Load event, since at the constructor level controls are under initialization process.
Set the focus to the ListView first whenever your form is displayed. The user typically sets focus to controls by clicking on them. However, you can also specify which controls gets the focus programmatically. One way of doing this is by setting the tab index of the control to 0 (the lowest value indicates the control that will have the initial focus). A second possibility is to use the following line of code in your form's Load event, or immediately after you set the Selected property:
listBox1.Select();
The problem with this solution is that the selected item will no longer appear highlighted when the user sets focus to a different control on your form (such as a textbox or a button).
For the second part of the question, selecting last added item in the ListBox, use the following code:
listBox1.SelectedIndex = listBox1.Items.Count - 1;
listBox1.SetFocus();
Looks like your ClearListBox method is actually a UpdateListBox method.
listBox1.DataSource = data;
listBox1.SelectedIndex = <index of newitem>;
// or
listBox1.SelectedItem = "text of new item";
listBox1.SetFocus();
If the new item is the last item, its index is listBox1.Items.Count - 1.
Is it possible to animate ListView items in C# ? The purpose is that I have a ListView that is asynchronously modified, and I'd like to animate smoothly the items that have been modified, until the user clicks them. This way items blinking, or whatever would do the trick, are the items that changed which haven't been reviewed yet.
Thank you guys !
UPDATE : sorry, i forgot. Never used WPF before, and I think it's too late to switch to it now. I'm using winforms.
I had to do something similar to what you are trying to do but instead of animating the ListView, I used custom checkbox layout in the list view to look different. The designer code for the ListView looks like:
this.listView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
this.listView1.Location = new System.Drawing.Point(104, 90);
this.listView1.MultiSelect = false;
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(264, 105);
this.listView1.Sorting = System.Windows.Forms.SortOrder.Ascending;
this.listView1.TabIndex = 7;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseClick);
Then I declared a list that will hold the selection of the user.
private IList<ListViewItem> m_CheckedItems = new List<ListViewItem>();
Here is the initialise method for the ListView. You need to manipulate here for the initial look of your listview.
private void InitialiseListView(IList<string> data)
{
listView1.Items.Clear();
m_CheckedItems.Clear();
listView1.Columns.Clear();
listView1.Columns.Add("Col1");
listView1.Columns[0].Width = listView1.Width;
ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection(listView1);
ImageList images = new ImageList();
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Unchecked);
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Checked);
listView1.SmallImageList = images;
foreach (string str in data)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = 0;
item.Text = str;
collection.Add(item);
}
}
This event triggers when the user selects an option in the list view. The selection is recorded in the list I created above and the checked image is displayed so that it looks like the user has selected the item.
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && listView1.FocusedItem != null)
{
if (listView1.FocusedItem.ImageIndex == 1)
{
listView1.FocusedItem.ImageIndex = 0;
m_CheckedItems.Remove(listView1.FocusedItem);
}
else
{
listView1.FocusedItem.ImageIndex = 1;
m_CheckedItems.Add(listView1.FocusedItem);
}
}
}
You can probably fiddle with fonts and forecolor of these items ... Each item within a List View is of type ListViewItem so you can individually manipulate it.
Hope this gives you some direction :)
You could use a MyListView: ListView and override OnDrawSubItem. Have the e.Item.Tag to store the "Clicked" state and update the background according to its state.
public partial class ObjectListView : ListView {
(....)
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if(bool)e.Item.Tag)
(...) animate
}
You can do that easier in WPF.
WPF Basic
http://msdn.microsoft.com/en-us/library/ms754130.aspx
http://en.wikipedia.org/wiki/Windows_Presentation_Foundation
WPF Animation
http://msdn.microsoft.com/en-us/library/ms752312.aspx
ListView animation sample
(WPF) Animate ListView item move
WPF ListView animation by reorder of items?