I am creating an application with C# WPF:
I have have dynamically generated treeview
In that treeview I have a TreeViewItem called BoekenLijst.
There I generate dynamic TreeViewItems like "the hobbit". The book content, like "the hobbit", I store in the Content property of a Checkbox.
So the treeview will look like this:
Treeview>
TreeViewItem(BoekenLijst)>
The Hobbit
Code Complete
I want to put the checked TreeViewItems in a ListBox so I did the following:
private List<TreeViewItem> GetAllItemContainers(TreeViewItem itemsControl)
{
List<TreeViewItem> allItems = new List<TreeViewItem>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
// try to get the item Container
TreeViewItem childItemContainer = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
// the item container maybe null if it is still not generated from the runtime
if (childItemContainer != null)
{
allItems.Add(childItemContainer);
List<TreeViewItem> childItems = GetAllItemContainers(childItemContainer);
foreach (TreeViewItem childItem in childItems)
{
CheckBox checkBoxTemp = childItem.Header as CheckBox;
if (checkBoxTemp != null)
optieListBox.Items.Add(checkBoxTemp.Content);
allItems.Add(childItem);
}
}
}
return allItems;
}
The problem is, it won't go into: foreach (TreeViewItem childItem in childItems)
Can someone help me out?
Have you tried running it through the Visual Studio debugger? If you insert the break point where the error is getting thrown, then you should be able to get a stack trace of what's going on, and if it's anything like the Eclipse debugger (I've not used VS in a while) you should be able to click on any variable in the code or hold the mouse over it, and it will display the current value of that variable in memory.
But I echo what the others say, try and figure out for yourself, take your time, and work through it step by step. I had to learn that myself, and I still get it wrong as I always forget to use the debugger...ask my team leader if you don't believe me :-)
Welshboy
Related
I have a list of checkbox controls
public List<CheckBox> _checkBoxes = new List<CheckBox>();
I add a certain amount during runtime using a switch case with the properties such as location and visibility.
This all works fine, but if I want the checkboxes to disappear or become checked at some point the GUI doesn't get updated, the list is updated with the new data but just not visually.
* I ended up doing this*
I created a bunch of checkboxes on the winfrom.Created a method which you can choose which ones are visible. Then created a method to fill a list of type checkbox. Then you can search the panel for control types, once you found the control, it can be manipulated. This isn't elegant and probably isn't the best way, but i am new to custom controls and winforms.
//search panel for checkboxes
foreach (Control c in panel1.Controls)
{
if (c is CustomControls.RoundedCheckBox)
{
CustomControls.RoundedCheckBox checkBox = c as CustomControls.RoundedCheckBox;
//if it is checked add to list
if(checkBox.Checked)
{
_checkBoxes.Add(checkBox);
}
}
}
If you have something like this:
_checkBoxes.Add(new CheckBox());
_checkBoxes[0].Parent = this;
then you should be able to manipulate your checkboxes from the list:
_checkBoxes[0].Checked = false;
But, the problem may occur if you do it in some kind of loop and want to see the results immediately.
In Windows there is something called message loop. Application simply works like that (pseudocode: TL;DR)
while(true)
{
message = GetFirstMessage();
if(message != null)
{
if(message.Id == CloseApplication)
break;
DispatchMessage(message);
RemoveFirstMessage();
}
}
So, application takes message from queue, then process it. A message is everything - button click, mouse move, paint... Everything.
So when a message is dispatched it looks for the control that should receive this message and then it does some work.
So, for example if you have something like that:
foreach(var ch in _checkBoxes)
{
ch.Checked = false;
DoSomeWorkThatTakesTime();
ch.Checked = true;
}
You won't see the change, because you are "trapped" in DispatchMessage. When you set Checked, you really sending a message. But this message cannot be Dispatched right now, because you are inside the foreach loop.
So the only thing you can do here is to tell your application - now, please DO READ message queue. In WinForms it's called "DoEvents", so this will do the work:
foreach(var ch in _checkBoxes)
{
ch.Checked = false;
Application.DoEvents();
DoSomeWorkThatTakesTime();
ch.Checked = true;
Application.DoEvents();
}
I have a WPF CheckBox inside a Popup, and I'm finding if it is inside the item template of a TreeView, then the CheckBox does not respond to user input. If it is outside of the TreeView, then there are no problems.
I have created a relatively minimal mock-up here:
https://github.com/logiclrd/TestControlsInPopupsNotWorking
Does anyone know why the CheckBox controls popped up from within the TreeView cannot be checked?
I think this is an oversight in the design of the TreeView. Take a look at this:
Note: Some code excerpts were tidied up to avoid wrapping.
// This method is called when MouseButonDown on TreeViewItem and also listen
// for handled events too. The purpose is to restore focus on TreeView when
// mouse is clicked and focus was outside the TreeView. Focus goes either to
// selected item (if any) or treeview itself
internal void HandleMouseButtonDown()
{
if (!this.IsKeyboardFocusWithin)
{
if (_selectedContainer != null)
{
if (!_selectedContainer.IsKeyboardFocused)
_selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the TreeView
this.Focus();
}
}
}
This method is called from TreeViewItem.OnMouseButtonDown, which we can see is a class-level handler that's configured to receive handled events too:
EventManager.RegisterClassHandler(
typeof(TreeViewItem),
Mouse.MouseDownEvent,
new MouseButtonEventHandler(OnMouseButtonDown),
/* handledEventsToo: */ true);
I have verified with the debugger that Handled is set to true by the time the event makes it to the TreeViewItem.
When you press down on the left mouse button over the CheckBox, the CheckBox begins a speculative 'click' operation and marks the event as handled. Normally, an ancestor element wouldn't see a handled event bubble up, but in this case it explicitly asked for them.
The TreeView sees that this.IsKeyboardFocusWithin resolves to false because the focused element is in another visual tree (the popup). It then gives focus back to the TreeViewItem.
Now, if you look in ButtonBase:
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
if (ClickMode == ClickMode.Hover)
{
// Ignore when in hover-click mode.
return;
}
if (e.OriginalSource == this)
{
if (IsPressed)
{
SetIsPressed(false);
}
if (IsMouseCaptured)
ReleaseMouseCapture();
IsSpaceKeyDown = false;
}
}
We see that IsPressed is set to false when focus is lost. If we then go to OnMouseLeftButtonUp, we see this:
bool shouldClick = !IsSpaceKeyDown && IsPressed && ClickMode == ClickMode.Release;
With IsPressed now false, the click operation never completes, all because the TreeViewItem stole focus away from you when you tried to click the button.
As a work-around, I have had success so far with using the NuGet library Ryder (which looks like a freely-usable open-source (MIT license) version of Microsoft Detours) to intercept the HandleMouseButtonDown method in TreeView.
The Ryder library can be found in the NuGet library, and the code behind it can be found here:
https://github.com/6A/Ryder
Hooking the HandleMouseButtonDown method is pretty simple:
var realMethod = typeof(System.Windows.Controls.TreeView).GetMethod("HandleMouseButtonDown", BindingFlags.Instance | BindingFlags.NonPublic);
var replacementMethod = typeof(Program).GetMethod(nameof(TreeView_HandleMouseButtonDown_shim), BindingFlags.Static | BindingFlags.NonPublic);
Redirection.Redirect(realMethod, replacementMethod);
The shim that replaces the method can basically do what the real method does but with a fix that detects the cross-visual-tree focus situation:
static void TreeView_HandleMouseButtonDown_shim(TreeView #this)
{
// Fix as seen in: https://developercommunity.visualstudio.com/content/problem/190202/button-controls-hosted-in-popup-windows-do-not-wor.html
if (!#this.IsKeyboardFocusWithin)
{
// BEGIN NEW LINES OF CODE
var keyboardFocusedControl = Keyboard.FocusedElement;
var focusPathTrace = keyboardFocusedControl as DependencyObject;
while (focusPathTrace != null)
{
if (ReferenceEquals(#this, focusPathTrace))
return;
focusPathTrace = VisualTreeHelper.GetParent(focusPathTrace) ?? LogicalTreeHelper.GetParent(focusPathTrace);
}
// END NEW LINES OF CODE
var selectedContainer = (System.Windows.Controls.TreeViewItem)TreeView_selectedContainer_field.GetValue(#this);
if (selectedContainer != null)
{
if (!selectedContainer.IsKeyboardFocused)
selectedContainer.Focus();
}
else
{
// If we don't have a selection - just focus the treeview
#this.Focus();
}
}
}
Some reflection is needed since this interacts with a private field that is not otherwise exposed from the TreeView class, but as work-arounds go, this is a lot less invasive than what I tried at first, which was importing the entirety of the TreeView class (and related types) from Reference Source into my project in order to alter the one member. :-)
Is this documentation still valid or am I missing something?
http://doc.xceedsoft.com/products/XceedWpfToolkit/Xceed.Wpf.Toolkit~Xceed.Wpf.Toolkit.PropertyGrid.PropertyGrid~SelectedObjects.html
PropertyGrid control does not appear to have SelectedObjects or SelectedObjectsOverride members. I'm using the latest version (2.5) of the Toolkit against .NET Framework 4.0.
UPDATE
#faztp12's answer got me through. For anyone else looking for a solution, follow these steps:
Bind your PropertyGrid's SelectedObject property to the first selected item. Something like this:
<xctk:PropertyGrid PropertyValueChanged="PG_PropertyValueChanged" SelectedObject="{Binding SelectedObjects[0]}" />
Listen to PropertyValueChanged event of the PropertyGrid and use the following code to update property value to all selected objects.
private void PG_PropertyValueChanged(object sender, PropertyGrid.PropertyValueChangedEventArgs e)
{
var changedProperty = (PropertyItem)e.OriginalSource;
foreach (var x in SelectedObjects) {
//make sure that x supports this property
var ProperProperty = x.GetType().GetProperty(changedProperty.PropertyDescriptor.Name);
if (ProperProperty != null) {
//fetch property descriptor from the actual declaring type, otherwise setter
//will throw exception (happens when u have parent/child classes)
var DeclaredProperty = ProperProperty.DeclaringType.GetProperty(changedProperty.PropertyDescriptor.Name);
DeclaredProperty.SetValue(x, e.NewValue);
}
}
}
Hope this helps someone down the road.
What i did when i had similar problem was I subscribed to PropertyValueChanged and had a List filled myself with the SelectedObjects.
I checked if the contents of the List where of the same type, and then if it is so, I changed the property in each of those item :
PropertyItem changedProperty = (PropertyItem)e.OriginalSource;
PropertyInfo t = typeof(myClass).GetProperty(changedProperty.PropertyDescriptor.Name);
if (t != null)
{
foreach (myClass x in SelectedItems)
t.SetValue(x, e.NewValue);
}
I used this because i needed to make a Layout Designer and this enabled me change multiple item's property together :)
Hope it helped :)
Ref Xceed Docs
In the code behind of my Silverlight application, I have the need to re-populate the TreeView and then make a specific TreeViewItem selected.
The code itself is pretty simple, here it is (i'll trim and pseudo-code-ify it to make it as short as possible)
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
}
That's basically it: i'm reading some data into myDataList, then i cycle through it and create as many TreeViewItems as needed in order to display the data.
Problem is, myTreeView.SelectedItem is null at the end of this, and SelectionChanged event isn't triggered. I would think that, since Items collection has been cleared and re-filled, switching IsSelected on one of the items would act like clicking, but it seems it doesn't).
Oddly enough (for me at least), issuing myTreeView.Items.First().IsSelected = true; by itself (that is, calling a method with that single line of code inside) works as expected: SelectedItem is there and all events are fired appropriateyl.
What's wrong with my code and/or what am I missing ? Looks like cleaning items up kind of breaks something.
I'm fairly sure others have had similar issues, but a bunch of searches I tried didn't help (most of the info & questions I came up with are WPF-related).
Thanks for your time, I'll provide more info if needed. Also, sorry for the wall of text.
UPDATE
Modifying code like this, now the method works as expected.
private void Button_Click()
{
Guid idToSelect = TellMeWhatToSelect();
List<myObject> myDataList = myObjectRepository.RetrieveData().ToList();
myTreeView.Items.Clear();
foreach(myObject o in myDataList)
{
myTreeView.Items.Add(new TreeViewItem() { Content = o.DataField, Tag = o.Id });
}
Dispatcher.BeginInvoke(()=>
{
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
});
}
Set property IsSelected inside of Dispatcher.BeginInvoke.
I had the same problem a while ago, I've solved by calling the UpdateLayout method from the treeview before setting the TreeViewItem as selected.Like this:
myTreeView.UpdateLayout();
myTreeView.Items.First(o => ((Guid)(o as TreeViewItem).Tag).Equals(idToSelect)).IsSelected = true;
I have a list of files in a ListView in WPF. Users can drag files onto the list view, and right now they are just appended to the end of the list. Is it possible to insert the file into the ListView right where the user dropped it?
WPF isn't really designed to be used that way. While you can brute force add ListViewItem's directly to the ListView, the way it's really supposed to work is that you have a collection of some kind (ObservableCollection<FileInfo> would work well) and bind the ListView's ItemsSource property to that collection.
Then the answer is simple. Instead of the Add method, you use the Insert method of the collection which takes an index.
As for finding which ListViewItem the mouse event occurred over, you could use the VisualTreeHelper.HitTest method.
From my point of view it is little tricky when I used the templated item. I have fight with it little bit. I am sharing my usecase which works with DraggableListBox. But I suppose the same solution works with ListBox control.
As the first I created the dependency object extension which is able to provide me ListItem element:
public static class WpfDomHelper
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
}
Then I implemented Drop logic which inserts(adds) item according specific Drop Y position of destination ListBoxItems:
private void Grid_Drop(object sender, DragEventArgs e)
{
int dropIndex = -1; // default position directong to add() call
// checking drop destination position
Point pt = e.GetPosition((UIElement)sender);
HitTestResult result = VisualTreeHelper.HitTest(this, pt);
if (result != null && result.VisualHit != null)
{
// checking the object behin the drop position (Item type depend)
var theOne = result.VisualHit.FindParent<Microsoft.TeamFoundation.Controls.WPF.DraggableListBoxItem>();
// identifiing the position according bound view model (context of item)
if (theOne != null)
{
//identifing the position of drop within the item
var itemCenterPosY = theOne.ActualHeight / 2;
var dropPosInItemPos = e.GetPosition(theOne);
// geting the index
var itemIndex = tasksListBox.Items.IndexOf(theOne.Content);
// decission if insert before or below
if (dropPosInItemPos.Y > itemCenterPosY)
{ // when drag is gropped in second half the item is inserted bellow
itemIndex = itemIndex + 1;
}
dropIndex = itemIndex;
}
}
.... here create the item .....
if (dropIndex < 0)
ViewModel.Items.Add(item);
else
ViewModel.Items.Insert(dropIndex, item);
e.Handled = true;
}
So this solution works with my template DraggableListBoxView, I suppose the same solution must work with standard ListBoxView. Good Luck
You can do this. It takes a bit of work, but it can be done. There are a couple demos out there, here is one on CodeProject. This particular one is by the wpf master known as Josh Smith. It's probably not exactly what you are looking for, but it should be pretty darn close.