I have got following problem:
There is a Ribbon-region in the Shell, let´s call it the "ShellRibbonRegion". There is also a task button region "ShellTaskButtonRegion" (similar to Creating View-Switching Applications with Prism 4).
There are e.g. three modules. Each module has a different number of RibbonTabItems. Module 1 has one RibbonTabItem, module 2 four and module 3 one.
The goal is now to add the RibbonTabItems to the "ShellRibbonRegion" after the "TaskButton" of a module is clicked. I already have written a custom RegionAdapter, but the problem is either only one RibbonTabItem (SingleActiveRegion) is shown or all (AllActiveRegion) RibbonTabItems from all modules.
public class RibbonRegionAdapter : RegionAdapterBase<Ribbon>
{
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="behaviorFactory">Allows the registration of the default set of RegionBehaviors.</param>
public RibbonRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory)
{
}
/// <summary>
/// Adapts a WPF control to serve as a Prism IRegion.
/// </summary>
/// <param name="region">The new region being used.</param>
/// <param name="regionTarget">The WPF control to adapt.</param>
protected override void Adapt(IRegion region, Ribbon regionTarget)
{
regionTarget.SelectedTabChanged += (sender, args) =>
{
if (regionTarget.SelectedTabItem == null)
return;
//region.Activate(regionTarget.SelectedTabItem);
};
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (UIElement element in e.NewItems)
{
if(element is Ribbon)
this.AddRibbon(element as Ribbon, regionTarget, region);
else if(element is RibbonTabItem)
this.AddRibbonTabItem(element as RibbonTabItem, regionTarget, region);
else if(element is Backstage)
this.AddBackstage(element as Backstage, regionTarget);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (element is Ribbon)
this.RemoveRibbon(element as Ribbon, regionTarget);
else if (element is RibbonTabItem)
this.RemoveRibbonTabItem(element as RibbonTabItem, regionTarget);
else if (element is Backstage)
this.RemoveBackstage(element as Backstage, regionTarget);
}
break;
}
};
}
#region Add
private void AddRibbon(Ribbon ribbon, Ribbon targetRibbon, IRegion region)
{
//Add tabs
foreach (var ribbonTabItem in ribbon.Tabs)
{
this.AddRibbonTabItem(ribbonTabItem, targetRibbon, region);
}
}
private void AddRibbonTabItem(RibbonTabItem ribbonTabItem, Ribbon targetRibbon, IRegion region)
{
if (!targetRibbon.Tabs.Contains(ribbonTabItem))
targetRibbon.Tabs.Add(ribbonTabItem);
}
private void AddBackstage(Backstage backstage, Ribbon targetRibbon)
{
}
#endregion
#region Remove
private void RemoveRibbon(Ribbon ribbon, Ribbon targetRibbon)
{
var tmp = new List<RibbonTabItem>(ribbon.Tabs);
//Add tabs
foreach (var ribbonTabItem in tmp)
{
if (targetRibbon.Tabs.Contains(ribbonTabItem)) this.RemoveRibbonTabItem(ribbonTabItem, targetRibbon);
}
}
private void RemoveRibbonTabItem(RibbonTabItem ribbonTabItem, Ribbon targetRibbon)
{
if (ribbonTabItem is IRegionMemberLifetime)
{
var rml = (IRegionMemberLifetime)ribbonTabItem;
if (!rml.KeepAlive) targetRibbon.Tabs.Remove(ribbonTabItem);
return;
}
targetRibbon.Tabs.Remove(ribbonTabItem);
}
private void RemoveBackstage(Backstage backstage, Ribbon targetRibbon)
{
}
#endregion
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
The desired behaviour is following:
The "TaskButton" of a module is clicked: All RibbonTabItems which don´t belong to this module are removed from the region and the tab items from "clicked" module are added.
How can I achieve this behaviour?
I had a similar problem and ended up keeping track of tabs added in a hashtable keyed by the module type name. The module tabs were developed in module views containing ribbon controls so the datacontext also needed to be transferred. Here is what I ended up with as my RibbonRegionAdapter (add your own namespace!):
// Based on
// http://www.codeproject.com/Articles/165370/Creating-View-Switching-Applications-with-Prism-4#AppendixA
// with my modifications.
/// <summary>
/// Enables use of a Ribbon control as a Prism region.
/// </summary>
/// <remarks> See Developer's Guide to Microsoft Prism (Ver. 4), p. 189.</remarks>
[Export]
public class RibbonRegionAdapter : RegionAdapterBase<Ribbon> {
private static readonly Hashtable RibbonTabs = new Hashtable();
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="behaviorFactory">Allows the registration
/// of the default set of RegionBehaviors.</param>
[ImportingConstructor]
public RibbonRegionAdapter(IRegionBehaviorFactory behaviorFactory)
: base(behaviorFactory) {}
/// <summary>
/// Adapts a WPF control to serve as a Prism IRegion.
/// </summary>
/// <param name="region">The new region being used.</param>
/// <param name="regionTarget">The WPF control to adapt.</param>
protected override void Adapt(IRegion region, Ribbon regionTarget) {
region.Views.CollectionChanged += (sender, e) => {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems) {
if (element is Ribbon) {
Ribbon rb = element as Ribbon;
var tabList = new List<RibbonTab>();
var items = rb.Items;
for (int i = rb.Items.Count - 1; i >= 0; i--) {
if (!(rb.Items[i] is RibbonTab)) continue;
RibbonTab rt = (rb.Items[i] as RibbonTab);
rb.Items.Remove(rt); // remove from existing view ribbon
regionTarget.Items.Add(rt); // add to target region ribbon
tabList.Add(rt); // add to tracking list
// Without these next 3 lines the tabs datacontext would end up being inherited from the Ribbon to which
// it has been transferred.
// Not sure if this is the best place to do this but it works for my purposes at the moment
if (rt.DataContext.Equals(regionTarget.DataContext)) { // then it is inherited
rt.DataContext = rb.DataContext; // so set it explicitly to the original parent ribbons datacontext
}
}
// store tracking list in hashtable using string key (= the view type name)
var key = rb.GetType().Name;
RibbonTabs[key] = tabList;
} else if (element is RibbonTab) {
// the datacontext should already be set in these circumstances
regionTarget.Items.Add(element);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems) {
var element = elementLoopVariable;
if (element is Ribbon) {
Ribbon rb = element as Ribbon;
var key = rb.GetType().Name;
if (!RibbonTabs.ContainsKey(key)) continue; // no ribbon tabs have been tracked
var tabList = (RibbonTabs[key] as List<RibbonTab>) ?? new List<RibbonTab>();
foreach (RibbonTab rt in tabList)
{
if (!regionTarget.Items.Contains(rt)) continue; // this shouldn't happen
regionTarget.Items.Remove(rt); // remove from target region ribbon
rb.Items.Add(rt); // restore to view ribbon
}
RibbonTabs.Remove(key); // finished tracking so remove from hashtable
} else if (regionTarget.Items.Contains(element)) {
regionTarget.Items.Remove(element);
}
}
break;
}
};
}
protected override IRegion CreateRegion() {
return new SingleActiveRegion();
}
}
Related
I have a class, Validator, which validates fields in all the Forms of a project.
I want to refer (from the function) the ToolTip on the form that contains the control which is being validated. This control is the function argument.
public static Boolean ValidateText(object sender)
{
//Error CS0039
ToolTip ttHelp = (sender as TextBox).FindForm().Controls["myToolTip"] as ToolTip;
if((sender as TextBox).Text == "") {
ttHelp.SetToolTIp(someControl,someMessage);
}
// [...]
}
Error CS0039 Cannot convert type 'System.Windows.Forms.Control' to 'System.Windows.Forms.ToolTip' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion
A ToolTip is not a Control, it's a Component, so you won't find it in a Form's Controls collection.
It's instead part of the System.ComponentModel.IContainer components private Field, usually defined in the Form's Designer section of the partial class.
To find the ToolTip of a control, you can use the ToolTip.GetToolTip([Control]) method, then verify whether the string returned is empty.
If you can access the Form's components Field - i.e., the ValidateText() method is called from the Form that contains the Control to validate - you can pass the components container to the method:
► If the Form has no Components, container (in both methods) will be null.
► If the Form has no ToolTip components, var toolTip will be null.
→ Keeping object sender, then casting to Control, since here you want to access only common Properties, like Text, which belong to the common Control class. You don't need to know sender's type: Control is the base type of all controls and exposes all the common properties.
bool result = ValidateText(this.someControl, this.components);
// [...]
public static bool ValidateText(object sender, IContainer container)
{
if (container == null) {
/* no components, decide what to do */
// [...]
}
var ctrl = sender as Control;
var toolTip = container.Components.OfType<ToolTip>()
.FirstOrDefault(tt => !string.IsNullOrEmpty(tt.GetToolTip(ctrl)));
if (toolTip != null) {
string tooltipText = toolTip.GetToolTip(ctrl);
// [...]
if (ctrl.Text == "") { }
return true;
}
return false;
}
Otherwise, you can access the components collection via reflection, in case the Control instance passed to the ValidateText() method may have an undetermined origin.
With [form].GetType().GetField("components").GetValue([form]) as IContainer we can access the components Field value then continue as before.
→ Here, sender is of type Control already, since this is the real nature of sender.
→ ValidateText([Control]) overloads the previous method. You can call ValidateText(sender, container) when you have assigned a value to container.
bool result = ValidateText(someControl);
// [...]
using System.Reflection;
public static bool ValidateText(Control sender)
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
Form form = sender.FindForm();
var container = form.GetType().GetField("components", flags).GetValue(form) as IContainer;
if (container == null) {
/* no components, decide what to do */
// [...]
}
// You can call ValidateText(sender, container) or continue
var toolTip = container.Components.OfType<ToolTip>()
.FirstOrDefault(tt => !string.IsNullOrEmpty(tt.GetToolTip(sender)));
if (toolTip != null) {
string tooltipText = toolTip.GetToolTip(sender);
// [...]
return true;
}
return false;
}
Please read Jimi's answer to understand why your code doesn't work.
Plus your Validator class can be refactored as follows to validate the TextBox controls in different ways. A single control, group of controls hosted by a Form or other container, and the controls of the OpenForms.
Also The ErrorProvider component is recommended for tasks as such. The Validator class provides both, the ToolTip and ErrorProvider services. Note that, the base class of the target controls is the TextBoxBase class, so you can validate any control derives from this base class such as; TextBox, RichTextBox, ToolStripTextBox...etc.
Please read the explanatory comments of each member.
internal sealed class Validator
{
private static Validator Current;
//Each Form has its own ToolTip...
private readonly Dictionary<Form, ToolTip> ToolTips;
private readonly ErrorProvider ErrPro;
private Validator()
{
ToolTips = new Dictionary<Form, ToolTip>();
ErrPro = new ErrorProvider();
}
static Validator() => Current = new Validator();
/// <summary>
/// Enable/disable the ToolTip service.
/// </summary>
public static bool ToolTipEnabled { get; set; } = true;
/// <summary>
/// Enable/disable the ErrorProvider service.
/// </summary>
public static bool ErrorProviderEnabled { get; set; } = false;
/// <summary>
/// Set/get the ToolTip/ErrorProvider message.
/// </summary>
public static string Message { get; set; } = "Hello World";
/// <summary>
/// Validate a single TextBox or RichTextBox control.
/// </summary>
/// <param name="txt">TextBox/RichTextBox..etc.</param>
public static void Validate(TextBoxBase txt)
{
if (txt is null) return;
var f = txt.FindForm();
if (f is null) return;
//Add a Form into the Dictionary and create a new ToolTip for it.
if (!Current.ToolTips.ContainsKey(f))
{
Current.ToolTips.Add(f, new ToolTip());
Current.ToolTips[f].ShowAlways = true; //Optional...
//Cleanup. Remove the closed Forms and dispose the related disposables.
f.HandleDestroyed += (s, e) =>
{
Current.ToolTips[f].Dispose();
Current.ToolTips.Remove(f);
if (Current.ToolTips.Count() == 0) Current.ErrPro.Dispose();
};
}
if (txt.Text.Trim().Length == 0)
{
if (ToolTipEnabled)
Current.ToolTips[f].SetToolTip(txt, Message);
if (ErroProviderEnabled)
Current.ErrPro.SetError(txt, Message);
}
else
{
Current.ToolTips[f].SetToolTip(txt, null);
Current.ErrPro.SetError(txt, null);
}
}
/// <summary>
/// An overload that takes a container.
/// </summary>
/// <param name="container">Form/Panel/GroupBox...etc</param>
public static void Validate(Control container)
{
if (container is null) return;
foreach (var c in GetAllControls<TextBoxBase>(container))
Validate(c);
}
/// <summary>
/// Validates the open Forms.
/// </summary>
public static void ValidateOpenForms()
{
foreach (var f in Application.OpenForms.Cast<Form>())
Validate(f);
}
/// <summary>
/// Clear and remove the messages explicitly.
/// </summary>
public static void Clear()
{
Current.ToolTips.Values.ToList().ForEach(x => x.RemoveAll());
Current.ErrPro.Clear();
}
/// <summary>
/// A recursive function to get the controls from a given container.
/// </summary>
private static IEnumerable<T> GetAllControls<T>(Control container)
{
var controls = container.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAllControls<T>(ctrl)).Concat(controls.OfType<T>());
}
}
Now you can use it as follows:
void TheCaller()
{
//Set the desired properties.
Validator.Message = "Hello World!";
Validator.ErrorProviderEnabled = true;
//Validate a single control.
Validator.Validate(textBox1);
//Or the controls of the current Form.
Validator.Validate(this);
//Or all the open Forms.
Validator.ValidateOpenForms();
}
anyone know how to raise an event on a ListBox when its redrawn. I'm trying to conditionally mask content in one column but the conditional check seems to be done before the listbox has been drawn and so the mask does not work because there is nothing to mask:
/// <summary>
/// Locks or unlocks the quantity textbox based on 100% flour and activates or deactivate weights
/// </summary>
private void activatePieceQuantity()
{
if (isFlour100Percent())
{
((TextBox)NumberOfItemsTextBox as TextBox).IsEnabled = true;
weightsActive(true);
}
else
{
((TextBox)NumberOfItemsTextBox as TextBox).IsEnabled = false;
weightsActive(false);
}
}
/// <summary>
/// Send controls to search with control name and activate or deactivate flag
/// </summary>
/// <param name="activate"></param>
private void weightsActive(bool activate)
{
int locationInList = 0;
foreach (RecipieIngredient ri in activeRecipie.RecipieIngredients)
{
SearchTree(this.IngredientsListBox.ItemContainerGenerator.ContainerFromIndex(locationInList), "QuanityWeight", activate);
locationInList++;
}
}
/// <summary>
/// Find all weight related objects in the ingredients list and set the visibility accordingly
/// </summary>
/// <param name="targetElement"></param>
/// <param name="flagName">Derived from the Tag of the textbox</param>
/// <param name="enableFlag"></param>
private void SearchTree(DependencyObject targetElement, string flagName, bool enableFlag)
{
if (targetElement == null)
return;
var count = VisualTreeHelper.GetChildrenCount(targetElement);
if (count == 0)
return;
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(targetElement, i);
if (child is TextBlock)
{
TextBlock targetItem = (TextBlock)child;
if (targetItem.Name == flagName)
if (enableFlag)
{
((TextBlock)targetItem as TextBlock).Visibility = Visibility.Visible;
return;
}
else
{
((TextBlock)targetItem as TextBlock).Visibility = Visibility.Collapsed;
}
}
else
{
SearchTree(child, flagName, enableFlag);
}
}
}
I got it now, the problem was that the ListBox was not drawn when the SearchTree function was called so there was never any DependencyObject to pass to it.
I solved the problem (somewhat hackish in my opinion) by placing a flag in the code to say that the check had been done and then calling the masking function from a LayoutUpdated event
private void IngredientsListBox_LayoutUpdated(object sender, EventArgs e)
{
if (ingredientsListLoaded)
{
activatePieceQuantity();
ingredientsListLoaded = false;
}
}
Is there a solution that involves a WPF TextBox/Block that auto scrolls to the end via binding? This can obviously be done in the code behind by calling the control directly, but how would one do this with Binding and MVVM?
in the code behind that works (but I'd like to avoid that and use the VM to do everything)
public void _readerService_BytesArrived(string s)
{
Action dispatcherAction = () =>
{
txtBoxOutPut.AppendText(s);
txtBoxOutPut.ScrollToEnd();
};
Dispatcher.Invoke(dispatcherAction);
}
I'm thinking that you're attempting to scroll to the end when the value of the Text changes inside of a TextBox/Block. Since this is a view-related operation, it should stay that way. Simply place a TextChanged event on the control and scroll to the end when the Text property changes.
Note that this basically means you need to split your operation... keep the binding on the view-model end, and place the ScrollToEnd in code-behind in your view... the view-model shouldn't care who is consuming the text string and how they behave.
An System.Windows.Interactivity Behavior may do just the trick for you. I use them for scrolling various controls and it's not in the VM but it's also not in the View and follows MVVM.
Below is an example for a Scrollviewer that may be helpful
public class FrameworkElementScrollviewerScrollingBehavior : Behavior<FrameworkElement>
{
private FrameworkElement _AssociatedElement;
private ScrollViewer _listboxScrollViewer = null;
#region OnAttached
protected override void OnAttached()
{
base.OnAttached();
_AssociatedElement = AssociatedObject;
_AssociatedElement.Loaded += OnControlLoaded;
_AssociatedElement.Unloaded += new RoutedEventHandler(_AssociatedElement_Unloaded);
//TODO: register/subscrive for event/message from the VM that tells you the scrollviewer to do something
}
//TODO: handle the event using the _AssociatedElement as the control you are acting on
void _AssociatedElement_Unloaded(object sender, RoutedEventArgs e)
{
Cleanup();
}
#endregion
#region OnDetaching
protected override void OnDetaching()
{
Cleanup();
base.OnDetaching();
}
#endregion
private bool _isCleanedUp;
private void Cleanup()
{
if (!_isCleanedUp)
{
_AssociatedElement.Loaded -= OnControlLoaded;
_AssociatedElement.Unloaded -= _AssociatedElement_Unloaded;
}
}
#region OnControlLoaded
private void OnControlLoaded(object sender, RoutedEventArgs args)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
_listboxScrollViewer = GetDescendantByType(sender as Visual, typeof(ScrollViewer)) as ScrollViewer;
if (_listboxScrollViewer.ComputedVerticalScrollBarVisibility == Visibility.Visible)
//do something when content is scrollable
}
}
#endregion
#region GetDescendantByType
/// <summary>
/// Gets the descendent of type
/// </summary>
/// <param name="element">The element.</param>
/// <param name="type">The type.</param>
/// <returns></returns>
public static Visual GetDescendantByType(Visual element, Type type)
{
if (element == null) return null;
if (element.GetType() == type) return element;
Visual foundElement = null;
if (element is FrameworkElement)
(element as FrameworkElement).ApplyTemplate();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = GetDescendantByType(visual, type);
if (foundElement != null)
break;
}
return foundElement;
}
#endregion
}
I might be suffering of Monday's dumbness, but I can't find a nice way of expanding all treeview nodes after I've added them in code behind (something like treeView.ExpandAll()).
Any quick help?
In xaml you could do it as follows :
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="TreeViewItem.IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
After playing around with all of the various methods for fully expanding and collapsing a tree view, by far the fastest method is the following. This method seems to work on very large trees.
Ensure your tree is virtualized, if it isn't virtualized then as soon as the tree gets to any kind of size it is going to become painfully slow whatever you do.
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
Assume that you have a view model backing your tree, each node on that view model that corresponds to a HierarchicalDataTemplate needs an IsExpanded property (it doesn't need to implement property changed). Assume these view models implement an interface like this:
interface IExpandableItem : IEnumerable
{
bool IsExpanded { get; set; }
}
The TreeViewItem style needs to be set as follows to bind the IsExpanded property in the view model to the view:
<Style
TargetType="{x:Type TreeViewItem}">
<Setter
Property="IsExpanded"
Value="{Binding
IsExpanded,
Mode=TwoWay}" />
</Style>
We are going to use this property to set the expansion state, but also, because the tree is virtualized this property is necessary to maintain the correct view state as the individual TreeViewItems get recycled. Without this binding nodes will get collapsed as they go out of view as the user browses the tree.
The only way to get acceptable speed on large trees is to work in code behind in the view layer. The plan is basically as follows:
Get hold of the current binding to the TreeView.ItemsSource.
Clear that binding.
Wait for the binding to actually clear.
Set the expansion state in the (now unbound) view model.
Rebind the TreeView.ItemsSource using the binding we cached in step 1.
Because we have virtualization enabled, performing a bind on TreeView.ItemsSource turns out to be very fast, even with a large view model. Likewise, when unbound updating the expansion state of the nodes should be very fast. This results in surprisingly fast updates.
Here is some code:
void SetExpandedStateInView(bool isExpanded)
{
var model = this.DataContext as TreeViewModel;
if (model == null)
{
// View model is not bound so do nothing.
return;
}
// Grab hold of the current ItemsSource binding.
var bindingExpression = this.TreeView.GetBindingExpression(
ItemsControl.ItemsSourceProperty);
if (bindingExpression == null)
{
return;
}
// Clear that binding.
var itemsSourceBinding = bindingExpression.ParentBinding;
BindingOperations.ClearBinding(
this.TreeView, ItemsControl.ItemsSourceProperty);
// Wait for the binding to clear and then set the expanded state of the view model.
this.Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new Action(() => SetExpandedStateInModel(model.Items, isExpanded)));
// Now rebind the ItemsSource.
this.Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new Action(
() => this.TreeView.SetBinding(
ItemsControl.ItemsSourceProperty, itemsSourceBinding)));
}
void SetExpandedStateInModel(IEnumerable modelItems, bool isExpanded)
{
if (modelItems == null)
{
return;
}
foreach (var modelItem in modelItems)
{
var expandable = modelItem as IExpandableItem;
if (expandable == null)
{
continue;
}
expandable.IsExpanded = isExpanded;
SetExpandedStateInModel(expandable, isExpanded);
}
}
WPF doesn't have an ExpandAll method. You'll need to loop through and set the property on each node.
See this question or this blog post.
I have done an ExpandAll that works also if your tree is set for virtualization (recycling items).
This is my code. Perhaps you should consider wrapping your hierarchy into a hierarchical model model view ?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using HQ.Util.General;
namespace HQ.Util.Wpf.WpfUtil
{
public static class TreeViewExtensions
{
// ******************************************************************
public delegate void OnTreeViewVisible(TreeViewItem tvi);
public delegate void OnItemExpanded(TreeViewItem tvi, object item);
public delegate void OnAllItemExpanded();
// ******************************************************************
private static void SetItemHierarchyVisible(ItemContainerGenerator icg, IList listOfRootToNodeItemPath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodeItemPath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodeItemPath.RemoveAt(0);
if (listOfRootToNodeItemPath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodeItemPath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodeItemPath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with ExpandItem is that this one open up the tree up to the target but will not expand the target itself,
/// while ExpandItem expand the target itself.
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">Any collectionic List. The collection should have every objet of the path to the targeted item from the root
/// to the target. For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2 (index 3)</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void SetItemHierarchyVisible(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
SetItemHierarchyVisible(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandItem(ItemContainerGenerator icg, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
Debug.Assert(icg != null);
if (icg != null)
{
if (listOfRootToNodePath.Count == 0) // nothing to do
return;
TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, always better to verify
{
listOfRootToNodePath.RemoveAt(0);
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
if (listOfRootToNodePath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodePath, onTreeViewVisible);
}
}
else
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
tvi = icgSender.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
if (tvi != null) // Due to threading, it is always better to verify
{
SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);
actionHolder.Execute();
}
};
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
return;
}
}
}
// ******************************************************************
/// <summary>
/// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem
/// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
/// This method should work for Virtualized and non virtualized tree.
/// The difference with SetItemHierarchyVisible is that this one open the target while SetItemHierarchyVisible does not try to expand the target.
/// (SetItemHierarchyVisible just ensure the target will be visible)
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="listOfRootToNodePath">The collection should have every objet of the path, from the root to the targeted item.
/// For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2</param>
/// <param name="onTreeViewVisible">Optionnal</param>
public static void ExpandItem(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
ExpandItem(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
}
// ******************************************************************
private static void ExpandSubWithContainersGenerated(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
foreach (object item in ic.Items)
{
var tvi = icg.ContainerFromItem(item) as TreeViewItem;
actionItemExpanded(tvi, item);
tvi.IsExpanded = true;
ExpandSubContainers(tvi, actionItemExpanded, referenceCounterTracker);
}
}
// ******************************************************************
/// <summary>
/// Expand any ItemsControl (TreeView, TreeViewItem, ListBox, ComboBox, ...) and their childs if any (TreeView)
/// </summary>
/// <param name="ic"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="referenceCounterTracker"></param>
public static void ExpandSubContainers(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
{
ItemContainerGenerator icg = ic.ItemContainerGenerator;
{
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
else if (icg.Status == GeneratorStatus.NotStarted)
{
ActionHolder actionHolder = new ActionHolder();
EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
{
var icgSender = sender as ItemContainerGenerator;
if (icgSender.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
// Never use the following method in BeginInvoke due to ICG recycling. The same icg could be
// used and will keep more than one subscribers which is far from being intended
// ic.Dispatcher.BeginInvoke(actionHolder.Action, DispatcherPriority.Background);
// Very important to unsubscribe as soon we've done due to ICG recycling.
actionHolder.Execute();
referenceCounterTracker.ReleaseRef();
}
};
referenceCounterTracker.AddRef();
actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
icg.StatusChanged += itemCreated;
// Next block is only intended to protect against any race condition (I don't know if it is possible ? How Microsoft implemented it)
// I mean the status changed before I subscribe to StatusChanged but after I made the check about its state.
if (icg.Status == GeneratorStatus.ContainersGenerated)
{
ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
}
}
}
}
// ******************************************************************
/// <summary>
/// This method is asynchronous.
/// Expand all items and subs recursively if any. Does support virtualization (item recycling).
/// But honestly, make you a favor, make your life easier en create a model view around your hierarchy with
/// a IsExpanded property for each node level and bind it to each TreeView node level.
/// </summary>
/// <param name="treeView"></param>
/// <param name="actionItemExpanded"></param>
/// <param name="actionAllItemExpanded"></param>
public static void ExpandAll(this TreeView treeView, Action<TreeViewItem, object> actionItemExpanded = null, Action actionAllItemExpanded = null)
{
var referenceCounterTracker = new ReferenceCounterTracker(actionAllItemExpanded);
referenceCounterTracker.AddRef();
treeView.Dispatcher.BeginInvoke(new Action(() => ExpandSubContainers(treeView, actionItemExpanded, referenceCounterTracker)), DispatcherPriority.Background);
referenceCounterTracker.ReleaseRef();
}
// ******************************************************************
}
}
And
using System;
using System.Threading;
namespace HQ.Util.General
{
public class ReferenceCounterTracker
{
private Action _actionOnCountReachZero = null;
private int _count = 0;
public ReferenceCounterTracker(Action actionOnCountReachZero)
{
_actionOnCountReachZero = actionOnCountReachZero;
}
public void AddRef()
{
Interlocked.Increment(ref _count);
}
public void ReleaseRef()
{
int count = Interlocked.Decrement(ref _count);
if (count == 0)
{
if (_actionOnCountReachZero != null)
{
_actionOnCountReachZero();
}
}
}
}
}
You have to include the following method in your project:
private void ExpandAllNodes(TreeViewItem treeItem)
{
treeItem.IsExpanded = true;
foreach (var childItem in treeItem.Items.OfType<TreeViewItem>())
{
ExpandAllNodes(childItem);
}
}
then, you only need to call it like this:
treeView.Items.OfType<TreeViewItem>().ToList().ForEach(ExpandAllNodes);
I need, for example, a way to disable all buttons in a form or validate all textboxes' data. Any ideas? Thanks in advance!
The simplest option may be to cascade:
public static void SetEnabled(Control control, bool enabled) {
control.Enabled = enabled;
foreach(Control child in control.Controls) {
SetEnabled(child, enabled);
}
}
or similar; you could of course pass a delegate to make it fairly generic:
public static void ApplyAll(Control control, Action<Control> action) {
action(control);
foreach(Control child in control.Controls) {
ApplyAll(child, action);
}
}
then things like:
ApplyAll(this, c => c.Validate());
ApplyAll(this, c => {c.Enabled = false; });
I prefer a lazy (iterator) approach to the problem, so this is what I use:
/// <summary> Return all of the children in the hierarchy of the control. </summary>
/// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
/// <param name="control"> The control that serves as the root of the hierarchy. </param>
/// <param name="maxDepth"> (optional) The maximum number of levels to iterate. Zero would be no
/// controls, 1 would be just the children of the control, 2 would include the children of the
/// children. </param>
/// <returns>
/// An enumerator that allows foreach to be used to process iterate all children in this
/// hierarchy.
/// </returns>
public static IEnumerable<Control> IterateAllChildren(this Control control,
int maxDepth = int.MaxValue)
{
if (control == null)
throw new ArgumentNullException("control");
if (maxDepth == 0)
return new Control[0];
return IterateAllChildrenSafe(control, 1, maxDepth);
}
private static IEnumerable<Control> IterateAllChildrenSafe(Control rootControl,
int depth,
int maxDepth)
{
foreach (Control control in rootControl.Controls)
{
yield return control;
// only iterate children if we're not too far deep and if we
// actually have children
if (depth >= maxDepth || control.Controls.Count == 0)
continue;
var children = IterateAllChildrenSafe(control, depth + 1, maxDepth);
foreach (Control subChildControl in children)
{
yield return subChildControl;
}
}
}
Also try:
public List<Control> getControls(string what, Control where)
{
List<Control> controles = new List<Control>();
foreach (Control c in where.Controls)
{
if (c.GetType().Name == what)
{
controles.Add(c);
}
else if (c.Controls.Count > 0)
{
controles.AddRange(getControls(what, c));
}
}
return controles;
}
private void Form1_Load(object sender, EventArgs e)
{
var c = getControls("Button", this);
}
I've been looking for a solution for the same to enable/disable controls based on type,so I came up with this similar to Luiscencio approach (You may also modify it to get all controls or change other properties).
public static void setEnabled (ControlCollection cntrList ,bool enabled,List<Type> typeList = null)
{
foreach (Control cntr in cntrList)
{
if (cntr.Controls.Count == 0)
if (typeList != null)
{
if (typeList.Contains(cntr.GetType()))
cntr.Enabled = enabled;
}
else
cntr.Enabled = enabled;
else
setEnabled(cntr.Controls, enabled, typeList);
}
}
public void loadFormEvents()
{
List<Type> list = new List<Type> ();
list.Add(typeof(TextBox));
setEnabled(frm.Controls ,false,list);
}