I set the BackColor of specific TreeNodes in a TreeView as a hint to the user that something interesting has happened to the node while they are using the application. However, when I set BackColor, it causes the entire parent TreeView control to redraw rather than just the label area of the specific TreeNode that has been changed. I am not calling Refresh or Update at any point -- just setting BackColor on the TreeNode. It seems that rather than just invalidating the bounds of the TreeNode that has been changed, the TreeView is refreshing its entire area. This results in an annoying quick flash of the control.
Any idea why this happening and if it can be easily stopped?
It doesn't look like you can stop this from occuring. I took a look at the code for the TreeNode.BackColor setter:
[SRDescription("TreeNodeBackColorDescr"), SRCategory("CatAppearance")]
public Color BackColor
{
get
{
if (this.propBag == null)
{
return Color.Empty;
}
return this.propBag.BackColor;
}
set
{
Color backColor = this.BackColor;
if (value.IsEmpty)
{
if (this.propBag != null)
{
this.propBag.BackColor = Color.Empty;
this.RemovePropBagIfEmpty();
}
if (!backColor.IsEmpty)
{
this.InvalidateHostTree();
}
}
else
{
if (this.propBag == null)
{
this.propBag = new OwnerDrawPropertyBag();
}
this.propBag.BackColor = value;
if (!value.Equals(backColor))
{
this.InvalidateHostTree();
}
}
}
}
Whenever the BackColor changes, an invalidate is forced on the tree that contains the node. Again, looking at the InvalidateHostTree function, there are no flags you can set to stop the refresh from occurring.
Related
how can I change the font for a currently selected text area inside a WPF RichTextBox?
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}
How about something like:
TextSelection text = richTextBox.Selection;
if (!text.IsEmpty)
{
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
Solved...
if (this.TextEditor.Selection.IsEmpty)
this.TextEditor.CurrentFontFamily = SelectedFont;
else
this.TextEditor.Selection.ApplyPropertyValue(TextElement.FontFamilyProperty, SelectedFont);
To get the current selection use:
Dim rng As TextRange = New TextRange(YourRtfBox.Selection.Start, YourRtfBox.Selection.End)
And then set the fontstyle:
rng.ApplyPropertyValue(Inline.FontSizeProperty, YourFontSizeValue)
rng.ApplyPropertyValue(Inline.FontFamilyProperty, YourFontFamilyValue)
To change the font family for a selection in the RichTextBox you should use this:
text.ApplyPropertyValue(Run.FontFamilyProperty, value);
The selected text in a RichTextBox is a Run object, so one must use the Run Dependency Properties.
This seems to work in Silverlight at least, so should be the same thing in WPF.
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.
I have UserControl in wpf 4.0 which contains buttons , labels , textboxes etc....
I want to loop those controls and when I get a buuton , I want to take it's name and save it to my list . Basically , all I want to do is to create a Names_list of all my buttons in the UserControl.
I have a method that iterates all the controls and if it finds a button , it saves it's name -
public void EnumVisual(Visual myVisual)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
Button _button = childVisual as Button;
if (_button != null)
{
Class_Button _newButtonClass = new Class_Button();
if (_button.Name != null)
{
_newButtonClass.ButtonName = _button.Name;
}
ButtonsList.Add(_newButtonClass);
}
// Enumerate children of the child visual object.
EnumVisual(childVisual);
}
}
I always get an empty list.
When I enter in to the code by debugging it and I watch the VisualTree of my UserControl , I see all the Panels and GroupBoxes and Grids but I dont see buttons , labels and texboxes although every control has a x:Name and every control is x:FieldModifier="public". This is very odd....And I cant understand the reason for that as well as how to solve this problem...
can anyone tell what I am doing wrong?
thanks
As suggested by #GazTheDestroyer you want to make sure the control template has been applied before trying to use VisualTreeHelper. Try:
public void EnumVisual(Visual myVisual)
{
if(myVisual is FrameworkElement)
((FrameworkElement)myVisual).ApplyTemplate();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
{
// Retrieve child visual at specified index value.
Visual childVisual = (Visual)VisualTreeHelper.GetChild(myVisual, i);
Button _button = childVisual as Button;
if (_button != null)
{
Class_Button _newButtonClass = new Class_Button();
if (_button.Name != null)
{
_newButtonClass.ButtonName = _button.Name;
}
ButtonsList.Add(_newButtonClass);
}
// Enumerate children of the child visual object.
EnumVisual(childVisual);
}
}
You can use a tool like Snoop
or WPF Inspector
to examine the visual tree of your control.
If these tools are able to do so, the error must be somewhere in your code, right?
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
}
how can I change the font for a currently selected text area inside a WPF RichTextBox?
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}
How about something like:
TextSelection text = richTextBox.Selection;
if (!text.IsEmpty)
{
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
Solved...
if (this.TextEditor.Selection.IsEmpty)
this.TextEditor.CurrentFontFamily = SelectedFont;
else
this.TextEditor.Selection.ApplyPropertyValue(TextElement.FontFamilyProperty, SelectedFont);
To get the current selection use:
Dim rng As TextRange = New TextRange(YourRtfBox.Selection.Start, YourRtfBox.Selection.End)
And then set the fontstyle:
rng.ApplyPropertyValue(Inline.FontSizeProperty, YourFontSizeValue)
rng.ApplyPropertyValue(Inline.FontFamilyProperty, YourFontFamilyValue)
To change the font family for a selection in the RichTextBox you should use this:
text.ApplyPropertyValue(Run.FontFamilyProperty, value);
The selected text in a RichTextBox is a Run object, so one must use the Run Dependency Properties.
This seems to work in Silverlight at least, so should be the same thing in WPF.