Define Background Color of Alternating TreeView Rows Based on Visibility - wpf

Is there a way in WPF to define the background of alternating visible rows?
I've tried setting the AlternationCount property, but that restarts for every child node which gives a weird look.
Ideally what I would like is to know what the visual index of a given node is. Only expanded nodes are counted.

There was no easy way to do this as WPF creates nested containers for the tree nodes. So as Rachel mentioned, looping through the items seemed to be the way to go. But I didn't want to depart too much from the built in ItemsControl.AlternationIndex attached property as that is the one people would expect. Because it is readonly I had to access it via reflection, but after that things fell into place.
First off, make sure you handle the Loaded, Expanded and Collapsed events of your TreeViewItem. In the event handler find the owning TreeView and do a recursive alternation count set of all visible nodes. I created an extension method to handle it:
public static class AlternationExtensions
{
private static readonly MethodInfo SetAlternationIndexMethod;
static AlternationExtensions()
{
SetAlternationIndexMethod = typeof(ItemsControl).GetMethod(
"SetAlternationIndex", BindingFlags.Static | BindingFlags.NonPublic);
}
public static int SetAlternationIndexRecursively(this ItemsControl control, int firstAlternationIndex)
{
var alternationCount = control.AlternationCount;
if (alternationCount == 0)
{
return 0;
}
foreach (var item in control.Items)
{
var container = control.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (container != null)
{
var nextAlternation = firstAlternationIndex++ % alternationCount;
SetAlternationIndexMethod.Invoke(null, new object[] { container, nextAlternation });
if (container.IsExpanded)
{
firstAlternationIndex = SetAlternationIndexRecursively(container, firstAlternationIndex);
}
}
}
return firstAlternationIndex;
}
}
As you can see it runs through each node and sets the custom alternation index. It checks if the node is expanded and if so continues the count on the child nodes.
Above I mentioned that you have to handle the Loaded event for the TreeViewItem. If you only handle the expanded and collapsed events you won't get the new containers that are created when a node is first opened. So you have to do a new pass when the child node has been created and added to the visual tree.

Something I've done with javascript is create an OnLoaded event for a table which loops through the table rows and if the row is visible, it sets the background color to a nextColor variable, and changes the nextColor variable to the opposite color. That might work here.

Related

Is it possible to create a control once and have it generated everytime it is needed?

Say for example you have a stackpanel that you would like to programmatically add buttons to.
The code behind for the button generation and addition to the stackpanel is:
Button button = new Button();
button.Content = "Button";
button.HorizontalAlignment = HorizontalAlignment.Left;
button.Name = "Button" + i
stackPanel1.Children.Add(button);
My question is - Is it possible to generate the button once and have it as some kind of template that can be added to the stackpanel whenever it is needed without going through the generation code again?
In WPF each UIElement can only be the logical child of one control at any given time, see WPF Error: Specified element is already the logical child of another element. Disconnect it first, so no you can't use the same button and add it to another control later on, unless you're sure you've gotten rid of that stackpanel
But, you can do recycling. See Optimizing Performance: Controls. especially if you are willing to override MeasureOverride and ArrangeOverride of you stackpanel
I've actually written such a recycler because I had grids with many controls, and I wanted to implement some kind of virtualizing grid. These are the main methods of my class:
internal class Recycler
{
private UIElementCollection _children;
public Recycler(UIElementCollection children)
{
_children = children;
//You need this because you're not going to remove any
//UIElement from your control without calling this class
}
public void Recycle(UIElement uie)
{
//Keep this element for recycling, remove it from Children
}
public UIElement GiveMeAnElement(Type type)
{
//Return one of the elements you kept of this type, or
//if none remain create one using the default constructor
}
}

Collapse all groups except the first one

I have a DataGrid with grouped ItemsSource. There are an expander on each group, so I can expand/collapse all the groups. Now, I'm trying to collapse all groups by default, but leave the first group expanded. The items source is dynamic, so I can't build any converter to check the group name. I must do it by group index.
Is it possible to do in in XAML? Or in code-behind?
This might be a little late, but in order to help with similar problems, defining a "Visual tree helper class" would be helpful in this case.
// the visual tree helper class
public static class VisualTreeHelper
{
public static Collection<T> GetVisualChildren<T>(DependencyObject current) where T : DependencyObject
{
if (current == null)
return null;
var children = new Collection<T>();
GetVisualChildren(current, children);
return children;
}
private static void GetVisualChildren<T>(DependencyObject current, Collection<T> children) where T : DependencyObject
{
if (current != null)
{
if (current.GetType() == typeof(T))
children.Add((T)current);
for (int i = 0; i < System.Windows.Media.VisualTreeHelper.GetChildrenCount(current); i++)
{
GetVisualChildren(System.Windows.Media.VisualTreeHelper.GetChild(current, i), children);
}
}
}
}
// then you can use the above class like this:
Collection<Expander> collection = VisualTreeHelper.GetVisualChildren<Expander>(dataGrid1);
foreach (Expander expander in collection)
expander.IsExpanded = false;
collection[0].IsExpanded = true;
the credit goes to this forum
I was able to solve this in my ViewModel.
The Expander is defined in the template of the DataGrids GroupStyle. The Binding must be TwoWay but triggered explicitly, so clicking in the View does not update the ViewModel. Thanks Rachel.
<Expander IsExpanded="{Binding DataContext.AreAllGroupsExpanded, RelativeSource={RelativeSource AncestorType={x:Type local:MyControl}}, UpdateSourceTrigger=Explicit}">
...
</Expander>
Then I can just set the property AreAllGroupsExpanded in my ViewModel.
I don't believe it can be done in the XAML, but it can be done in code-behind. Here is one solution that I tested in Silverlight. It should probably work just as well in WPF.
// If you don't have a direct reference to the grid's ItemsSource,
// then cast the grid's ItemSource to the type of the source.
// In this example, I used a PagedCollectionView for the source.
PagedCollectionView pcv = (PagedCollectionView)myDataGrid.ItemsSource;
// Using the PagedCollectionView, I can get a reference to the first group.
CollectionViewGroup firstGroup = (CollectionViewGroup)pcv.Groups[0];
// First collapse all groups (if they aren't already collapsed).
foreach (CollectionViewGroup group in pcv.Groups)
{
myDataGrid.ScrollIntoView(group, null); // This line is a workaround for a problem with collapsing groups when they aren't visible.
myDataGrid.CollapseRowGroup(group, true);
}
// Now expand only the first group.
// If using multiple levels of grouping, setting 2nd parameter to "true" will expand all subgroups under the first group.
myDataGrid.ExpandRowGroup(firstGroup, false);
// Scroll to the top, ready for the user to see!
myDataGrid.ScrollIntoView(firstGroup, null);

Winforms treeview, recursively check child nodes problem

The following code is taken direct from Microsoft at http://msdn.microsoft.com/en-us/library/system.windows.forms.treeview.aftercheck%28VS.80%29.aspx.
// Updates all child tree nodes recursively.
private void CheckAllChildNodes(TreeNode treeNode, bool nodeChecked)
{
foreach (TreeNode node in treeNode.Nodes)
{
node.Checked = nodeChecked;
if (node.Nodes.Count > 0)
{
// If the current node has child nodes, call the CheckAllChildsNodes method recursively.
this.CheckAllChildNodes(node, nodeChecked);
}
}
}
// NOTE This code can be added to the BeforeCheck event handler instead of the AfterCheck event.
// After a tree node's Checked property is changed, all its child nodes are updated to the same value.
private void node_AfterCheck(object sender, TreeViewEventArgs e)
{
// The code only executes if the user caused the checked state to change.
if (e.Action != TreeViewAction.Unknown)
{
if (e.Node.Nodes.Count > 0)
{
/* Calls the CheckAllChildNodes method, passing in the current
Checked value of the TreeNode whose checked state changed. */
this.CheckAllChildNodes(e.Node, e.Node.Checked);
}
}
}
You put it in a form containing a treeview and call node_AfterCheck on (surprise, surprise), the treeview AfterCheck event. It then recursively checks or unchecks the child nodes on the treeview.
However if you actually try it, and click several times on the same treeview check box fast enough, the child nodes end up with their check out-of-sync with the parent. You probably need a couple of levels of children with perhaps 100 children in-total for the UI update to be slow enough to notice this happening.
I've tried a couple of things (such as disabling the treeview control at the beginning of node_AfterCheck and re-enabling at the end), but the out-of-sync problem still happens.
Any ideas?
The .NET TreeView class heavily customizes mouse handling for the native Windows control in order to synthesize the Before/After events. Unfortunately, they didn't get it quite right. When you start clicking fast, you'll generate double-click messages. The native control responds to a double-click by toggling the checked state for the item, without telling the .NET wrapper about it. You won't get a Before/AfterCheck event.
It's a bug but they won't fix it. The workaround is not difficult, you'll need to prevent the native control from seeing the double-click event. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox, replacing the existing one.
using System;
using System.Windows.Forms;
class MyTreeView : TreeView {
protected override void WndProc(ref Message m) {
// Filter WM_LBUTTONDBLCLK
if (m.Msg != 0x203) base.WndProc(ref m);
}
}
Using the solution above, I think it is need to paint more detailed steps, how to apply it for those who want to apply it to an already created TreeView. For example, for me, a beginner, this caused difficulties, but here is the solution:
Creating a class "NoClickTree.cs" in your project.
Include this code in new class:
public class NoClickTree : TreeView
{
protected override void WndProc(ref Message m)
{
// Suppress WM_LBUTTONDBLCLK
if (m.Msg == 0x203) { m.Result = IntPtr.Zero; }
else base.WndProc(ref m);
}
}
Go to Form1.Designer.cs or "yourWindowWithTreeView".Designer.cs
Find original initialization at the end of the file, something like private System.Windows.Forms.TreeView treeView;
Replace them on private NoClickTree treeView;
In function private void InitializeComponent() find original initialization, something like this.treeView = new System.Windows.Forms.TreeView();
Replace them on this.treeView = new NoClickTree();
Done!
This steps helped me for solve this problem.

select tabItem programmatically in WPF

I have different tabItems in a TabControl
and each tabItem has some input fields.
I am moving between the tabItems programmatically (like a wizard to move from the first to the next)
I am using this code inside the "Next" button
tabItem2.isSelected = true;
my problem that when I move between the tabItems by clicking on them, the focus (keyboard focus) will move to the first textbox input.
But programmatically with the previous code, the focus won't move to the first input textbox item inside the tabItem.
Any idea?
If you're forcing the IsSelected property, I'd also give the first TextBox a name and set the focus after you set the selected tab.
If you're building your UI dynamically, this won't work, but you can create a utility method which searches the logical tree (or the visual tree if you're using presenters/view-models) for the first input control and then set the focus.
These solutions didn't work for me. It got as far selecting the TabItem I wanted, but it wasn't able to select/focus the desired TreeViewItem. (It would only focus the TVI if the TabItem was already selected.) The solution below finally worked for me.
(FYI: The snippets below are part of app that is similar to Microsoft Help Viewer 2.0. When you click the "Sync" button, it first selects the Contents tab if not already selected, then traverses into tree view until it finds the matching tree view item. Which it then selects/focuses.)
Cheers
private void OnClick_SyncContents(object sender, RoutedEventArgs e)
{
// If the help-contents control isn't visible (ie., some other tab is currently selected),
// then use our common extension method to make it visible within the tab control. Once
// it visible, the extension method will call the event handler passed (which is this method)
if (!this.m_UcHelpFileContents.IsVisible)
{
this.m_UcHelpFileContents.
SelectParentTabItem_WaitForMeToBecomeVisible_ThenCallThisEventHandlerWithNullArguments
(this.OnClick_SyncContents);
}
else
{
// Else the help-contents control is currently visible, thus focus the
// matching tree view item
/* Your code here that focuses the desired tree view item */
}
}
public static class CommonExtensionMethods
{
public static void
SelectParentTabItem_WaitForMeToBecomeVisible_ThenCallThisEventHandlerWithNullArguments
(this FrameworkElement frameworkElement, RoutedEventHandler eventHandlerToCallWhenVisible)
{
// First, define the handler code for when the given framework element becomes visible
DependencyPropertyChangedEventHandler HANDLER = null;
HANDLER = (s, e) =>
{
// If here, the given framework element is now visible and its tab item currently selected
// Critical: first and foremost, undo the latch to is-visible changed
frameworkElement.IsVisibleChanged -= HANDLER;
// Now invoke the event handler that the caller wanted to invoke once visible
frameworkElement.Dispatcher.BeginInvoke(eventHandlerToCallWhenVisible, null, null);
};
// Use our common extension method to find the framework element's parent tab item
TabItem parentTabItem = frameworkElement.GetFirstParentOfType<TabItem>();
if (parentTabItem != null)
{
// Assign the handler to the given framework element's is-visible-changed event
frameworkElement.IsVisibleChanged += HANDLER;
// Now set the tab item's is-selected property to true (which invokes the above
// handler once visible)
parentTabItem.IsSelected = true;
}
}
public static T GetFirstParentOfType<T>
(this FrameworkElement frameworkElement) where T : FrameworkElement
{
for (FrameworkElement fe = frameworkElement.Parent as FrameworkElement;
fe != null;
fe = fe.Parent as FrameworkElement)
{
if (fe is T)
return fe as T;
}
// If here, no match
return null;
}
}

Silverlight TreeView - Load data when node is expanded

Is there a way to only load child nodes when the parent node is expanded? The problem that I’m running into is that the “expand” icon doesn’t show up if a node doesn’t have any children. Since I don’t want to load the children until the icon is clicked, I’m left with a bit of a catch 22.
First, read this post:
http://bea.stollnitz.com/blog/?p=55
Second, inherit TreeViewItem and TreeView:
public class TreeViewItemEx : TreeViewItem {
protected override DependencyObject GetContainerForItemOverride() {
TreeViewItemEx tvi = new TreeViewItemEx();
Binding expandedBinding = new Binding("IsExpanded");
expandedBinding.Mode = BindingMode.TwoWay;
tvi.SetBinding(TreeViewItemEx.IsExpandedProperty, expandedBinding);
return tvi;
}
}
public class TreeViewEx : TreeView {
protected override DependencyObject GetContainerForItemOverride() {
TreeViewItemEx tvi = new TreeViewItemEx();
Binding expandedBinding = new Binding("IsExpanded");
expandedBinding.Mode = BindingMode.TwoWay;
tvi.SetBinding(TreeViewItemEx.IsExpandedProperty, expandedBinding);
return tvi;
}
}
Third, binding your Model's property to "IsExpanded".
With tree views you usually have to load the children of each displayed node.
So if you only display the root you need to load the roots children too. Once you expand the root you need to load the children of each child if you want the expand stuff for those children.
It is perfectly possible to have tree controls load the child nodes on demand, and you can do this with the Silverlight TreeView. When you populate the root nodes if the data for this is coming from a database for example then for each node also return whether it has children or not, if it does then add one dummy child, this will make the control put a + next to it. Handle the expanded event and in this see if the child is the dummy node, if it is remove it, get the children from the database and add them.
i was also looking at this. I think you need to write your own subclass of the TreeNode that loads the child nodes on demand.
One approach I used in a windows forms TreeView was to add an empty child node to each node and then remove this when the node was expanded and the real child nodes were needed. The problem with this approach is that you get false expandable nodes, but if you can live with it then its a simple solution.

Resources