When I set SelectedNode to null the tree updates correctly but BeforeSelect and AfterSelect do not fire.
Is there any way to tell when the selection has been changed to null?
My first thought is to extend the control and add an event though I would have thought something like this would already be available.
I think your solution is good. However, I just discovered this control (keep an eye on SO's right column :)):
http://treeviewadv.sourceforge.net/
which supports what you're looking for, maybe has other goodies too...
It seems the only way I could do this was to create a new control and provide a new implementation for SelectedNode, even OnAfterSelect and OnBeforeSelect weren't getting called.
public new TreeNode SelectedNode {
get { return base.SelectedNode; }
set {
// Remember, if `value' is not null this will be called in `base'.
if (value == null) {
TreeViewCancelEventArgs args
= new TreeViewCancelEventArgs(value, false, TreeViewAction.Unknown);
OnBeforeSelect(args);
if (args.Cancel)
return;
}
base.SelectedNode = value;
// Remember, if `value' is not null this will be called in `base'.
if (value == null) {
OnAfterSelect(new TreeViewEventArgs(value, TreeViewAction.Unknown));
}
}
}
Related
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 it possible to attach multiple layout events to a PdfPCell?
I read that there's a method for setting a cell event: http://api.itextpdf.com/itext/com/itextpdf/text/pdf/PdfPCell.html#setCellEvent(com.itextpdf.text.pdf.PdfPCellEvent)
I'm not sure if it's possible to set multiple events. I would like to seperate different cell options in seperate events based on my business logic. Sometimes I want to draw an ellipse in it, sometimes a square (or anything else).It would be nice if I could simply attach the events that I need.
Thanks for any response!
Yes, you can add multiple cell events to a cell. This is the Java code of the setCellEvent() method:
public void setCellEvent(PdfPCellEvent cellEvent) {
if (cellEvent == null) {
this.cellEvent = null;
} else if (this.cellEvent == null) {
this.cellEvent = cellEvent;
} else if (this.cellEvent instanceof PdfPCellEventForwarder) {
((PdfPCellEventForwarder) this.cellEvent).addCellEvent(cellEvent);
} else {
PdfPCellEventForwarder forward = new PdfPCellEventForwarder();
forward.addCellEvent(this.cellEvent);
forward.addCellEvent(cellEvent);
this.cellEvent = forward;
}
}
If you pass null, then all existing events are removed from the cell. If no cell event was present, a new cell event is added. If there is already a cell event present, a PdfPCellEventForwarder is created. This is a class that stores different cell events and that eventually will execute all these events one by one.
Update:
iTextSharp (C#) is kept in sync with iText (Java), so this functionality also works for iTextSharp. I have just checked the iTextSharp code and I've found this:
virtual public IPdfPCellEvent CellEvent {
get {
return this.cellEvent;
}
set {
if (value == null) this.cellEvent = null;
else if (this.cellEvent == null) this.cellEvent = value;
else if (this.cellEvent is PdfPCellEventForwarder) ((PdfPCellEventForwarder)this.cellEvent).AddCellEvent(value);
else {
PdfPCellEventForwarder forward = new PdfPCellEventForwarder();
forward.AddCellEvent(this.cellEvent);
forward.AddCellEvent(value);
this.cellEvent = forward;
}
}
}
So there is no need to create your own PdfPCellEventForwarder (although you may do so if you want to), iTextSharp will take care of creating a PdfPCellEventForwarder in your place if you add multiple events to a PdfPCell.
Here's a curious piece of behaviour. We recently built some code in a WPF MVVM application that looked a bit like this:
foreach (var mA in Preferences.Where(itm => itm.Preference == "Y"))
{
Member m = _members.FirstOrDefault(itm => itm.MemberID == mA.MemberAvertedID);
if (m != null)
{
m.Selected = true;
}
}
Members = _members;
So, FirstOrDefault fetches a reference to a Member, which is updated. Members and _members are effectively the same - the former is a property, wrapping the latter as a private variable, with an event fire:
public ObservableCollection<Member> Members
{
get
{return _members;}
set
{
_members = value;
OnPropertyChanged("Members");
}
}
The purpose of setting Members to _members was simply to get the event to fire - but it didn't work. As you stepped through, the OnPropertyChanged event fired, but the application didn't respond. This, however, does work:
foreach (var mA in Preferences.Where(itm => itm.Preference == "Y"))
{
Members m = _members.FirstOrDefault(itm => itm.MemberID == mA.MemberAvertedID);
if (m != null)
{
mtc.Selected = true;
}
}
var mem = new ObservableCollection<Members>(_members);
Members = mem;
I'm assuming that what's going on here is that because setting Members to _members is effectively the property setting itself, the code "presumes" that nothing has changed, and skips the event. But I'm not really satisfied with that explanation. Can anyone elucidate further as to what's going on here?
Bindings to ObservableCollections will not rebind the list unless the collection reference differs, hence the last piece of code is working. That said, unless you are actually adding or removing items from the underlying _members list you shouldn't have to rebind the whole list.
So (I assume) if your goal is to refresh the state of the Selected Member, you are probably lacking a OnPropertyChanged("Selected") raised from within the Selected property.
In summary: the property that is changing must be named in the PropertyChanged event. And the event must come from the object that owns the property. In this case, notifying WPF of a change to the Selected property requires raising the event on the Member instance.
I am getting the error "reference not set to an instance of an object" when the following code occurs on startup:
switch (Popup_Data_Type_ComboBox.SelectedItem.ToString())
{
I am pretty sure that this error is occurring as Popup_Data_Type_ComboBox has not yet been created therefore its not possible to get the sting value. How Can I get around this problem?
Ok thanks a lot for all the help I threw in a check if Popup_Data_Type_ComboBox.SelectedItem == null and it now works fine
Add a check before the switch, assuming the code is in a method that just handles the Popup_Data_Type_ComboBox.SelectionChanged-event or the likes:
if (Popup_Data_Type_ComboBox == null
|| Popup_Data_Type_ComboBox.SelectedIndex < 0)
{
// Just return from the method, do nothing more.
return;
}
switch (...)
{
}
The most likely issue is that your combo box hasn't been created, or doesn't have a selected item. In this case, you'd have to explicitly handle that:
if (Popup_Data_Type_ComboBox != null && Popup_Data_Type_ComboBox.SelectedItem != null)
{
switch (Popup_Data_Type_ComboBox.SelectedItem.ToString())
{
//...
}
}
else
{
// Do your initialization with no selected item here...
}
I'd verify first that Popup_Data_Type_ComboBox is instantiated, and then verify that an item is selected. If you are running this on startup as you said, then it is likely no item is selected. you can check with:
if(Popup_Data_Type_ComboBox.SelectedItem != null)
{
switch (Popup_Data_Type_ComboBox.SelectedItem.ToString())
{
//.....
}
}
I have an control that inherits from another control (TxTextControl). I have a SelectedText property that basicaly wraps the base SelectedText property, which is apparently needed because my control is implementing an interface with that property. The code is this:
public string SelectedText
{
get
{
return base.Selection.Text; // Error here (#1042)
}
set
{
if (base.Selection == null)
{
base.Selection = new TXTextControl.Selection(0, 0);
}
base.Selection.Text = value;
}
}
When I drop this control on a form, no problems. It compiles and runs. Everything looks great. However, when I save, close then reopen the form, the form designer shows this error:
Object reference not set to an instance of an object.
1. Hide Call Stack
at Test.FormattedTextBox2.get_SelectedText() in C:\Projects\Test\FormattedTextBox2.cs:line 1042
Anyone know what is going on? I'm about to pull out my last hair...
UPDATE:
darkassisin93's answer wasn't exactly correct, but that was because my posted code wasn't exactly accurate. I needed to test if base.Selection was null before attempting to access a property of that object. In any case, that answer got me headed in the right direction. Here is the actual solution:
public string SelectedText
{
get
{
string selected = string.Empty;
if (base.Selection != null)
{
selected = base.Selection.Text;
}
return selected;
}
set
{
if (base.Selection == null)
{
base.Selection = new TXTextControl.Selection(0, 0);
// Have to check here again..this apparently still
// results in a null in some cases.
if (base.Selection == null) return;
}
base.Selection.Text = value;
}
}
Try replacing
return base.SelectedText;
with
return base.SelectedText ?? string.Empty;
It's most likely because the base class's SelectedText property is set to null.