WPF - Hit Test Filter Callback - wpf

I have a canvas and it has child DrawingVisuals in its VisualCollection. I want to hit test against some type of child but not for others. To do that I wrote HitTestFilterCallback function:
public HitTestFilterBehavior MyHitTestFilter(DependencyObject o)
{
Debug.WriteLine(o.GetType());
if (o is BackgroundLine)
{
return HitTestFilterBehavior.ContinueSkipSelf;
}
else
{
return HitTestFilterBehavior.Continue;
}
}
So I check whether the child of canvas is a BackgroundLine, which is derived from DrawingVisual, and if it is I skip it. However, the type I am getting from Debug.WriteLine(o.GetType()) is only System.Windows.Media.DrawingVisual. Is there a way I can find the most specific object type?
Rest of the code is below. I want to test against GraphicsBase objects only.
GraphicsBase hit = null;
public HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
hit = (GraphicsBase)result.VisualHit;
return HitTestResultBehavior.Stop;
}
VisualTreeHelper.HitTest(drawingCanvas, new HitTestFilterCallback(MyHitTestFilter),
new HitTestResultCallback(MyHitTestResult), new PointHitTestParameters(point));
if (hit != null)
Debug.WriteLine("hit");
else
Debug.WriteLine("nothing");

I found the problem. The DrawingVisual object I am seeing was the rectangle I added for background color. I forgot about that and thought I was getting BackgroundLine object's type as DrawingVisual. I can get the specific BackgroundLine type as rooks said. Thanks.

Related

Why can not update autocomplete suggestions?

I am using two AutocompleteTextFilters as depended filters. I want the second one filter to change its options depending on the suggestion of the first filter.
I have bind an event listener on the first filter so as when it loose focus it triggers a proccess on the second filter.
The proble is that the second filter never changes its options. I even have setup hardcoded values in case somethig was wrong on my code but no luck.
The code I use is below:
public CreateSubmission(com.codename1.ui.util.Resources resourceObjectInstance, Map<String, ProjectType> projectTypes) {
this.projectTypes = projectTypes;
initGuiBuilderComponents(resourceObjectInstance);
gui_ac_projecttype.clear();
gui_ac_projecttype.setCompletion( this.projectTypes.keySet().toArray( new String[0]) );
gui_ac_projecttype.addFocusListener( new ProjectTypeFocusListener( this ));
gui_ac_steps.setCompletion( new String[]{"t10", "t20"});
}
public void makeSteps (String selection) {
ProjectType projectType = this.projectTypes.get( selection );
if (projectType != null) {
this.selectedProjectType = selection;
int length = projectType.projectSteps.length;
String[] steps = new String[ length ];
for(int i =0; i < length; i ++) {
steps[i] = projectType.projectSteps[i].projectStep;
}
// String[] s = gui_ac_steps.getCompletion();
gui_ac_steps.setCompletion( new String[]{"t1", "t2"} );
gui_ac_steps.repaint();
}
else {
}
}
public class ProjectTypeFocusListener implements FocusListener{
private CreateSubmission parent;
public ProjectTypeFocusListener( CreateSubmission parent ) {
this.parent = parent;
}
#Override
public void focusGained(Component cmp) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void focusLost(Component cmp) {
this.parent.makeSteps (
((AutoCompleteTextField)cmp).getText()
);
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
On the above code the initialization happens on "public CreateSubmission" method.
"gui_ac_projecttype" is the first AutocompletionTextField that triggers the whole proccess through it's FocusListener handler (class ProjectTypeFocusListener )
"gui_ac_steps" is the second AutocompleteTextField filter that must change its values. On the code above I initialize it's suggestions to "t10", "t20". Those two values are shown correctly.
Later from iside the FoculListenerHandler's method "ProjectTypeFocusListener.focusLost" I call method "makeSteps" which sets the suggestion options to "t1", "t2 and then I repaint the component. These two last values are never shown. It remains on the first values "t10", "t20".
The Strange thing is that in debugger when I ask gui_ac_steps.getCompletion(); to see the current options ( the code that is commentd out into makeSteps method) I get the correct values "t1", "t2".
But on the screen it keeps showing "t10", "t20".
any help is aprreciated.
You shouldn't do anything "important" in a focus listener. Especially not with a text field. They are somewhat unreliable because the text field switches to native editing and in effect transfers the focus there. The problem is that some events are delayed due to the back and forth with the native editing so by the time the focus event is received you've moved on to the next field.
Try something like this for this specific use case https://www.codenameone.com/blog/dynamic-autocomplete.html

How do you determine (WinForms) control type in UITestControl?

I'm trying to make custom properties for controls available in Coded UI Tests and every example I've found is totally useless.
For instance: http://msdn.microsoft.com/en-us/library/hh552522.aspx
public override int GetControlSupportLevel(UITestControl uiTestControl)
{
// For MSAA, check the control type
if (string.Equals(uiTestControl.TechnologyName, "MSAA",
StringComparison.OrdinalIgnoreCase) &&
(uiTestControl.ControlType == "Chart"||uiTestControl.ControlType == "Text"))
{
return (int)ControlSupport.ControlSpecificSupport;
}
// This is not my control, so return NoSupport
return (int)ControlSupport.NoSupport;
}
// Get the property value by parsing the accessible description
public override object GetPropertyValue(UITestControl uiTestControl, string propertyName)
{
if (String.Equals(propertyName, "State", StringComparison.OrdinalIgnoreCase))
{
object[] native = uiTestControl.NativeElement as object[];
IAccessible acc = native[0] as IAccessible;
string[] descriptionTokens = acc.accDescription.Split(new char[] { ';' });
return descriptionTokens[1];
}
// this is not my control
throw new NotSupportedException();
}
This code is entirely worthless if you have 2 different controls that are "Text" controls - there is no way to determine which type of text control it is. The "ControlType" property is very misleading because it does not return the Type of the control as its name suggests. It's more like a control category. How can you determine what the control actually is?
You can use something like this. Hope this helps.
string controlType = control.GetProperty(XamlControl.PropertyNames.ControlType).ToString();

Visual Tree Finder is returning null while searching for Data Grid

I have Tab Control which has many tab items, I am checking Data Grid Items Count while closing tab items. For the first time it works fine(I mean in first iteration). After closing one tab item, in second iteration sellDtg is null. Does anyone know why it is happening? I am concerning that this is UI problem, layout is not being refreshed. Please help me or show direction.
while (tc.HasItems)
{
TabItem ti = tc.SelectedItem as TabItem;
if (ti.Header == "Продажа")
{
Microsoft.Windows.Controls.DataGrid sellDtg = FindChild<Microsoft.Windows.Controls.DataGrid>(tc, "SellDataGrid");
if (sellDtg.Items.Count > 0)
{
Sell sl = new Sell();
if (Sell.basketfromSellDateListBox == false)
{
sl.ClearBasket(sellDtg);
Sell.ClearFromSellBasket((int)sellDtg.Tag);
}
}
}
if (ti != null)
tc.Items.Remove(ti);
}
Thanks in advance!!!
I've written a simple FindChildLogical function in analogy for LogicalTreeHelper below:
public static T FindChildLogical<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
if (parent == null) return null;
var child = LogicalTreeHelper.FindLogicalNode(parent, childName);
return (T)child;
}
and you call it as:
Microsoft.Windows.Controls.DataGrid sellDtg = FindChildLogical<Microsoft.Windows.Controls.DataGrid>(ti, "SellDataGrid");
I hope it gets you where you intend to.
I am going to assume your FindChild method uses the VisualTreeHelper to find its children.
In the first iteration, the TabItem's Content has been through a layout pass, and is visible. This means that the TabItem's Content will be in the visual tree.
However, for the other tab items, their Content hasn't been through a layout pass (it is only added to the visual tree when it's parent gets selected, and this has to then go through a layout/render pass), and won't be in the visual tree.
There are a couple of ways to get the child content of a TabItem that hasn't been through a layout pass as the selected tab:
1) You can try using the LogicalTreeHelper to find the Grid you're looking for (and you will likely have to search the Content of the TabItem, not the TabControl).
2) You can take your code out of the while loop, and do a callback on the dispatcher at the Loaded priority:
void RemoveAllItems()
{
if (!tc.HasItems) return;
TabItem ti = tc.SelectedItem as TabItem;
if (ti.Header == "Продажа")
{
var sellDtg = FindChild<Microsoft.Windows.Controls.DataGrid>(tc, "SellDataGrid");
if (sellDtg.Items.Count > 0)
{
Sell sl = new Sell();
if (Sell.basketfromSellDateListBox == false)
{
sl.ClearBasket(sellDtg);
Sell.ClearFromSellBasket((int)sellDtg.Tag);
}
if (ti != null)
tc.Items.Remove(ti);
}
}
Dispatcher.BeginInvoke(new Action(RemoveAllItems), DispatcherPriority.Loaded);
}
If you use the second method, you will likely be able to see the tab items removed one at a time, which may be something you don't want to see.

Overwrite registeredName

I've a method that when called returns a LinearGradientBrush with random color for the GradientStop and I use on them mylabel.RegisterName for later use in a storyboard animation.
To prevent the error on first call where no registered name are present I do this:
try
{
myLabel.UnregisterName("GS1");
myLabel.UnregisterName("GS2");
myLabel.UnregisterName("GS3");
myLabel.UnregisterName("GS4");
}
catch
{
}
I have not found a way to overwrite registered name. There's a better way to do this?
What about UnregisterName?
(That's what I get for attempting to answer when I can only read part of the question...)
Digging around a bit more, may have found another (WAY simpler) alternative:
var isGs1Defined = NameScope.GetNameScope(myLabel).FindName("GS1") == null;
or packaged up nicely:
public static bool IsNameRegistered(DependencyObject depObject, string name)
{
var namescope = NameScope.GetNameScope(depObject);
if(namescope == null)
return false;
return namescope.FindName(name) != null;
}

ItemContainerGenerator.ContainerFromItem() returns null?

I'm having a bit of weird behavior that I can't seem to work out. When I iterate through the items in my ListBox.ItemsSource property, I can't seem to get the container? I'm expecting to see a ListBoxItem returned, but I only get null.
Any ideas?
Here's the bit of code I'm using:
this.lstResults.ItemsSource.ForEach(t =>
{
ListBoxItem lbi = this.lstResults.ItemContainerGenerator.ContainerFromItem(t) as ListBoxItem;
if (lbi != null)
{
this.AddToolTip(lbi);
}
});
The ItemsSource is currently set to a Dictionary and does contain a number of KVPs.
I found something that worked better for my case in this StackOverflow question:
Get row in datagrid
By putting in UpdateLayout and a ScrollIntoView calls before calling ContainerFromItem or ContainerFromIndex, you cause that part of the DataGrid to be realized which makes it possible for it return a value for ContainerFromItem/ContainerFromIndex:
dataGrid.UpdateLayout();
dataGrid.ScrollIntoView(dataGrid.Items[index]);
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
If you don't want the current location in the DataGrid to change, this probably isn't a good solution for you but if that's OK, it works without having to turn off virtualizing.
Finally sorted out the problem... By adding VirtualizingStackPanel.IsVirtualizing="False" into my XAML, everything now works as expected.
On the downside, I miss out on all the performance benefitst of the virtualization, so I changed my load routing to async and added a "spinner" into my listbox while it loads...
object viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
if (viewItem == null)
{
list.UpdateLayout();
viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
Debug.Assert(viewItem != null, "list.ItemContainerGenerator.ContainerFromItem(item) is null, even after UpdateLayout");
}
Step through the code with the debugger and see if there is actually nothing retured or if the as-cast is just wrong and thus turns it to null (you could just use a normal cast to get a proper exception).
One problem that frequently occurs is that when an ItemsControl is virtualizing for most of the items no container will exist at any point in time.
Also i would not recommend dealing with the item containers directly but rather binding properties and subscribing to events (via the ItemsControl.ItemContainerStyle).
Use this subscription:
TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
TheListBox.Dispatcher.Invoke(() =>
{
var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0);
if (TheOne != null)
// Use The One
});
};
I'm a bit late for the party but here's another solution that's fail-proof in my case,
After trying many solutions suggesting to add IsExpanded and IsSelected to underlying object and binding to them in TreeViewItem style, while this mostly works in some case it still fails ...
Note: my objective was to write a mini/custom Explorer-like view where when I click a folder in the right pane it gets selected on the TreeView, just like in Explorer.
private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = sender as ListViewItem;
var node = item?.Content as DirectoryNode;
if (node == null) return;
var nodes = (IEnumerable<DirectoryNode>)TreeView.ItemsSource;
if (nodes == null) return;
var queue = new Stack<Node>();
queue.Push(node);
var parent = node.Parent;
while (parent != null)
{
queue.Push(parent);
parent = parent.Parent;
}
var generator = TreeView.ItemContainerGenerator;
while (queue.Count > 0)
{
var dequeue = queue.Pop();
TreeView.UpdateLayout();
var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
if (queue.Count > 0) treeViewItem.IsExpanded = true;
else treeViewItem.IsSelected = true;
generator = treeViewItem.ItemContainerGenerator;
}
}
Multiple tricks used in here:
a stack for expanding every item from top to bottom
ensure to use current level generator to find the item (really important)
the fact that generator for top-level items never return null
So far it works very well,
no need to pollute your types with new properties
no need to disable virtualization at all.
Although disabling virtualization from XAML works, I think it's better to disable it from the .cs file which uses ContainerFromItem
VirtualizingStackPanel.SetIsVirtualizing(listBox, false);
That way, you reduce the coupling between the XAML and the code; so you avoid the risk of someone breaking the code by touching the XAML.
Most probably this is a virtualization-related issue so ListBoxItem containers get generated only for currently visible items (see https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9)
If you are using ListBox I'd suggest switching to ListView instead - it inherits from ListBoxand it supports ScrollIntoView() method which you can utilize to control virtualization;
targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
(the example above also utilizes the DoEvents() static method explained in more detail here; WPF how to wait for binding update to occur before processing more code?)
There are a few other minor differences between the ListBox and ListView controls (What is The difference between ListBox and ListView) - which should not essentially affect your use case.
VirtualizingStackPanel.IsVirtualizing="False" Makes the control fuzzy . See the below implementation. Which helps me to avoid the same issue.
Set your application VirtualizingStackPanel.IsVirtualizing="True" always.
See the link for detailed info
/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
if (container != null)
{
if (container.DataContext == item)
{
return container as TreeViewItem;
}
// Expand the current container
if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
{
container.SetValue(TreeViewItem.IsExpandedProperty, true);
}
// Try to generate the ItemsPresenter and the ItemsPanel.
// by calling ApplyTemplate. Note that in the
// virtualizing case even if the item is marked
// expanded we still need to do this step in order to
// regenerate the visuals because they may have been virtualized away.
container.ApplyTemplate();
ItemsPresenter itemsPresenter =
(ItemsPresenter)container.Template.FindName("ItemsHost", container);
if (itemsPresenter != null)
{
itemsPresenter.ApplyTemplate();
}
else
{
// The Tree template has not named the ItemsPresenter,
// so walk the descendents and find the child.
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
}
}
Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
// Ensure that the generator for this panel has been created.
UIElementCollection children = itemsHostPanel.Children;
MyVirtualizingStackPanel virtualizingPanel =
itemsHostPanel as MyVirtualizingStackPanel;
for (int i = 0, count = container.Items.Count; i < count; i++)
{
TreeViewItem subContainer;
if (virtualizingPanel != null)
{
// Bring the item into view so
// that the container will be generated.
virtualizingPanel.BringIntoView(i);
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
}
else
{
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
// Bring the item into view to maintain the
// same behavior as with a virtualizing panel.
subContainer.BringIntoView();
}
if (subContainer != null)
{
// Search the next level for the object.
TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
{
return resultContainer;
}
else
{
// The object is not under this TreeViewItem
// so collapse it.
subContainer.IsExpanded = false;
}
}
}
}
return null;
}
For anyone still having issues with this, I was able to work around this issue by ignoring the first selection changed event and using a thread to basically repeat the call. Here's what I ended up doing:
private int _hackyfix = 0;
private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
{
//HACKYFIX:Hacky workaround for an api issue
//Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the
//first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
//with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need I ignore the event twice just in case but I think you can get away with ignoring only the first one.
if (_hackyfix == 0 || _hackyfix == 1)
{
_hackyfix++;
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
OnMediaSelectionChanged(sender, e);
});
}
//END OF HACKY FIX//Actual code you need to run goes here}
EDIT 10/29/2014: You actually don't even need the thread dispatcher code. You can set whatever you need to null to trigger the first selection changed event and then return out of the event so that future events work as expected.
private int _hackyfix = 0;
private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
{
//HACKYFIX: Daniel note: Very hacky workaround for an api issue
//Microsoft's api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the
//first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
//with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need
if (_hackyfix == 0)
{
_hackyfix++;
/*
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
OnMediaSelectionChanged(sender, e);
});*/
return;
}
//END OF HACKY FIX
//Your selection_changed code here
}

Resources