I have a WPF form which has many controls on it. Many (but not all) of these controls are databound to an underlying object. At certain times, such as when the Save button is pressed, I need to check all the validation rules of my controls. Is there a way to do this programatically, WITHOUT hard-coding a list of the controls to be validated? I want this to continue to work after another developer adds another control and another binding, without having to update some list of bindings to be refreshed.
In a nutshell, is there any way to retrieve the collection of all data bindings from a WPF window?
Try out my sample below. I haven't fully tested this so it may have issues. Also, performance may be questionable. Maybe others can help out to make it faster. But anyway, it seems to do the trick.
Note: A limitation to this, however, is that it may not pick up the bindings defined within Styles or DataTemplates. I'm not sure though. Needs more testing.
Anyway, the solution has three parts basically:
Use VisualTreeHelper to walk the entire visual tree.
For each item in the visual tree, get all dependency properties. Reference.
Use BindingOperations.GetBindingBase to get the binding for each property.
GetBindingsRecursive function:
void GetBindingsRecursive(DependencyObject dObj, List<BindingBase> bindingList)
{
bindingList.AddRange(DependencyObjectHelper.GetBindingObjects(dObj));
int childrenCount = VisualTreeHelper.GetChildrenCount(dObj);
if (childrenCount > 0)
{
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(dObj, i);
GetBindingsRecursive(child, bindingList);
}
}
}
DependencyObjectHelper class:
public static class DependencyObjectHelper
{
public static List<BindingBase> GetBindingObjects(Object element)
{
List<BindingBase> bindings = new List<BindingBase>();
List<DependencyProperty> dpList = new List<DependencyProperty>();
dpList.AddRange(GetDependencyProperties(element));
dpList.AddRange(GetAttachedProperties(element));
foreach (DependencyProperty dp in dpList)
{
BindingBase b = BindingOperations.GetBindingBase(element as DependencyObject, dp);
if (b != null)
{
bindings.Add(b);
}
}
return bindings;
}
public static List<DependencyProperty> GetDependencyProperties(Object element)
{
List<DependencyProperty> properties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.DependencyProperty != null)
{
properties.Add(mp.DependencyProperty);
}
}
}
return properties;
}
public static List<DependencyProperty> GetAttachedProperties(Object element)
{
List<DependencyProperty> attachedProperties = new List<DependencyProperty>();
MarkupObject markupObject = MarkupWriter.GetMarkupObjectFor(element);
if (markupObject != null)
{
foreach (MarkupProperty mp in markupObject.Properties)
{
if (mp.IsAttached)
{
attachedProperties.Add(mp.DependencyProperty);
}
}
}
return attachedProperties;
}
}
Sample usage:
List<BindingBase> bindingList = new List<BindingBase>();
GetBindingsRecursive(this, bindingList);
foreach (BindingBase b in bindingList)
{
Console.WriteLine(b.ToString());
}
There is a better solution in .NET 4.5 and above:
foreach (BindingExpressionBase be in BindingOperations.GetSourceUpdatingBindings(element))
{
be.UpdateSource();
}
Related
I have multiple pictureboxes and I need to load random images into them during runtime. So I thought it would be nice to have a collection of all pictureboxes and then assign images to them using a simple loop. But how should I do it? Or maybe are there any other better solutions to such problem?
Using a bit of LINQ:
foreach(var pb in this.Controls.OfType<PictureBox>())
{
//do stuff
}
However, this will only take care of PictureBoxes in the main container.
You could use this method:
public static IEnumerable<T> GetControlsOfType<T>(Control root)
where T : Control
{
var t = root as T;
if (t != null)
yield return t;
var container = root as ContainerControl;
if (container != null)
foreach (Control c in container.Controls)
foreach (var i in GetControlsOfType<T>(c))
yield return i;
}
Then you could do something like this:
foreach (var pictureBox in GetControlsOfType<PictureBox>(theForm)) {
// ...
}
If you're at least on .NET 3.5 then you have LINQ, which means that since ControlCollection implements IEnumerable you can just do:
var pictureBoxes = Controls.OfType<PictureBox>();
I use this generic recursive method:
The assumption of this method is that if the control is T than the method does not look in its children. If you need also to look to its children you can easily change it accordingly.
public static IList<T> GetAllControlsRecusrvive<T>(Control control) where T :Control
{
var rtn = new List<T>();
foreach (Control item in control.Controls)
{
var ctr = item as T;
if (ctr!=null)
{
rtn.Add(ctr);
}
else
{
rtn.AddRange(GetAllControlsRecusrvive<T>(item));
}
}
return rtn;
}
A simple function, easy to understand, recursive, and it works calling it inside any form control:
private void findControlsOfType(Type type, Control.ControlCollection formControls, ref List<Control> controls)
{
foreach (Control control in formControls)
{
if (control.GetType() == type)
controls.Add(control);
if (control.Controls.Count > 0)
findControlsOfType(type, control.Controls, ref controls);
}
}
You can call it on multiple ways.
To get the Buttons:
List<Control> buttons = new List<Control>();
findControlsOfType(typeof(Button), this.Controls, ref buttons);
To get the Panels:
List<Control> panels = new List<Control>();
findControlsOfType(typeof(Panel), this.Controls, ref panels);
etc.
Here's another version since the existing provided ones weren't quite what I had in mind. This one works as an extension method, optionally, and it excludes checking the root/parent container's type. This method is basically a "Get all descendent controls of type T" method:
public static System.Collections.Generic.IEnumerable<T> ControlsOfType<T>(this System.Web.UI.Control control) where T: System.Web.UI.Control{
foreach(System.Web.UI.Control childControl in control.Controls){
if(childControl is T) yield return (T)childControl;
foreach(var furtherDescendantControl in childControl.ControlsOfType<T>()) yield return furtherDescendantControl;
}
}
public static List<T> FindControlByType<T>(Control mainControl,bool getAllChild = false) where T :Control
{
List<T> lt = new List<T>();
for (int i = 0; i < mainControl.Controls.Count; i++)
{
if (mainControl.Controls[i] is T) lt.Add((T)mainControl.Controls[i]);
if (getAllChild) lt.AddRange(FindControlByType<T>(mainControl.Controls[i], getAllChild));
}
return lt;
}
This to me is by far the easiest. In my application, I was trying to clear all the textboxes in a panel:
foreach (Control c in panel.Controls)
{
if (c.GetType().Name == "TextBox")
{
c.Text = "";
}
}
Takes Control as container into account:
private static IEnumerable<T> GetControlsOfType<T>(this Control root)
where T : Control
{
if (root is T t)
yield return t;
if (root is ContainerControl || root is Control)
{
var container = root as Control;
foreach (Control c in container.Controls)
foreach (var i in GetControlsOfType<T>(c))
yield return i;
}
}
I have build a WPF User Control which contains a ComboBox with a custom popup window which contains a User Control the inner control (the one in the popup) has some properties that I want to expose in the main user control so the host page can read and write to the inner control.
I am having trouble doing this is there something I am doing wrong or is what I am doing ill advised ?
Regards Christian Andersen
you can try exposing it using this
public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) yield break;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var children = child as T;
if (children != null)
{
yield return children;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
How I use it
var uc = (TabItem)sender;
foreach (TextBlock textBlock in uc.FindVisualChildren<TextBlock>())
{
textBlock.Foreground = Brushes.WhiteSmoke;
}
Using the standard listbox I would like to bind it to a set of objects and update a bound items collection to include those selected.
So I have something like:
_pContext = new BindingSource();
_pContext.DataSource = _gpContext;
_pContext.DataMember = "ParentEntities";
_AllChildrenListBox.DataSource = _container.ChildEntities;
_AllChildrenListBox.DataBindings.Add("MySelectedItems", _pContext, "ChildEntities", false);
_allChildrenListBox is the listbox. I created a new listbox type inheriting from ListBox so I could create an alternative SelectedItems property which would then encapsulate the logic to set/unset the items.
So the question in a nutshell is: In the above, ChildEntities is a collection of "ChildEntity" objects. My list box contains all possible ChildEntity objects and I want the elements in ChildEntities to be selected, and updated when selection changed.
I have found a way around this. It's a little ugly at the moment, but works. I can refine it later.
So first bit was the binding itself.
_AllChildrenListBox.DataBindings.Add("SelectedItems2", _pContext, "ChildEntities2", true);
The key here seemed to be setting the formatting parameter to true.
Next I needed something in SelectedItems2 that would do my dirty work. This is a mess, but works. Here's the full class:
public class MyListBox : ListBox
{
private IEnumerable<object> _original;
public IEnumerable<object> SelectedItems2
{
get
{
return UpdateSet();
}
set
{
SelectItems(value);
}
}
private IEnumerable<object> UpdateSet()
{
var listSource = _original as IListSource;
IList list = null;
if (listSource != null)
{
list = listSource.GetList();
}
var iList = _original as IList;
if (list == null && iList != null)
{
list = iList;
}
if (list == null)
{
return _original;
}
foreach (var item in SelectedItems)
{
if (!list.Contains(item))
{
list.Add(item);
}
}
foreach (var item in _original.ToList())
{
if (!SelectedItems.Contains(item))
{
list.Remove(item);
}
}
return _original;
}
private void SelectItems(IEnumerable<object> items)
{
_original = items;
var hashset = new HashSet<object>();
foreach (var item in items)
{
hashset.Add(item);
}
for(var i=0;i<Items.Count;i++)
{
SetSelected(i, hashset.Contains(Items[i]));
}
}
}
Essentially I'm breaking all the rules and assuming IEnumerable hides a more tasty list based interface, if it does it uses that to changes the underlying set.
Finally, I'm using EF (Entity Framework) and you can't call the setter of a collection property, so my entity currently looks like this: Notice how I'm duplicating all the list change operations which is totally unnecessary, but I did say this was just the first working go.
public partial class ParentEntity
{
public IEnumerable<object> ChildEntities2
{
get
{
return new List<object>(ChildEntities);
}
set
{
if (value == null)
{
return;
}
foreach (var item in ChildEntities.ToList().Where(item => !value.Contains(item)))
{
ChildEntities.Remove(item);
}
foreach (var item in value)
{
var cItem = item as ChildEntity;
if (cItem != null)
{
if (!ChildEntities.Contains(item as ChildEntity))
{
ChildEntities.Add(item as ChildEntity);
}
}
}
}
}
}
I have Accordion which is bound to ObservableCollection. I need to apply workaround to make Accordion resize its children to the content (ie if an item has been deleted from the bound collection I need the accordion to shrink, and if added - to expand).
However all the workaround I found use AccordionItem objects. They all have AccordionItem items set in XAML so their accordion.Items are collections of AccordionItem objects.
Although I am binding to myObject they are placed in AccordionItem object in the ItemContainerStyleTemplate. The only thing I need is to access that AccordionItem somehow. If I try something like accordion.Items[0].GetType() it returns myObject.
So the question is - how do I access AccordionItem object from data bound Accordion?
The workaround I wanted to try: (EDIT: It does work as I needed)
public static void UpdateSize(this AccordionItem item)
{
item.Dispatcher.BeginInvoke(
delegate
{
if (!item.IsLocked && item.IsSelected)
{
item.IsSelected = false;
item.InvokeOnLayoutUpdated(delegate { item.IsSelected = true; });
}
});
}
I've had to do similar things to Accordions, and the only way I was able to get down to the AccordionItems was by walking the visual tree.
Here's how I did it: Given these extension methods :
public static IEnumerable<DependencyObject> GetAllChildrenOfType(this DependencyObject depObject, Type t, bool recursive = true)
{
List<DependencyObject> objList = new List<DependencyObject>();
var childrenList = depObject.GetChildren();
foreach (DependencyObject i in childrenList)
{
Type ct = i.GetType();
if (ct == t)
objList.Add(i);
if (recursive)
objList.AddRange(i.GetAllChildrenOfType(t));
}
return objList.ToArray();
}
public static IEnumerable<DependencyObject> GetChildren(this DependencyObject depObject)
{
int count = depObject.GetChildrenCount();
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(depObject, i);
}
}
Now you can get all the AccordionItems in a given Accordion:
var accordionItemList = myAccordion.GetAllChildrenOfType(typeof(AccordionItem));
foreach (AccordionItem i in accordionItemList)
{...}
This may be a bit more complicated than needed, in my instance I had an accordion within the accordion, which made things difficult in the end.
I have the synfusion tile view as below.
Maximized Item template for these Items is TreeView. the treeview items Source is bind to the Observable Collection. When I Maximize one of these Items, it will load the data from ViewModel as below. It's in the MaximizedItemChanged Event.
private void tileViewControl_Exchanges_MaximizedItemChanged(object sender, TileViewEventArgs args)
{
if (args.Source != null)
{
TileViewControl tileViewControl = (TileViewControl)sender;
TileViewItem tvi = (TileViewItem)args.Source;
PanelViewModel panelViewModel = (PanelViewModel)tileViewControl.ItemContainerGenerator.ItemFromContainer(tvi);
String currentSelectedPanelID = GetSelectedPanelID(tileViewControl);
// below function will load all the treeview items.
SetSelectedExchangeID(tileViewControl, exchangePanelViewModel.ExchangeID);
}
}
But treeview has over thousands of items. So after clicking on Maximize, It will take a while and the program hang. Is there a way to maximize the Item smoothly first and load the Treeview Item at the background? What I'd like to do is I will show the loading animation while the treeview is loading but now, when it maximized (after hanging for 8 or 9 secs) , the treeview is already loaded.
Edit: I added the code fore SetSelectedExchangeID.
public static readonly DependencyProperty SelectedExchangeIDProperty =
DependencyProperty.RegisterAttached("SelectedExchangeID",
typeof(String),
typeof(UC_Contract_List),
new UIPropertyMetadata(new PropertyChangedCallback(SelectedExchangeIDPropertyChanged)));
static void SelectedExchangeIDPropertyChanged(
DependencyObject depObj,
DependencyPropertyChangedEventArgs eventArgs)
{
TileViewControl tileViewControl = (TileViewControl)depObj;
ItemContainerGenerator itemContainerGenerator = tileViewControl.ItemContainerGenerator;
String newPanelID = (String)eventArgs.NewValue;
if (newPanelID != null)
{
if (tileViewControl.Visibility == Visibility.Visible)
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
if (exchangePanel.ExchangeID.Equals(newExchangeID))
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
try
{
if (tileViewControl.tileViewItems != null)
{
if (tvi.TileViewItemState != TileViewItemState.Maximized)
{
tvi.TileViewItemState = TileViewItemState.Maximized;
}
}
}
catch (Exception e) { }
break;
}
}
}
}
else
{
foreach (PanelViewModel exchangePanel in tileViewControl.Items)
{
TileViewItem tvi = (TileViewItem)itemContainerGenerator.ContainerFromItem(exchangePanel);
tvi.TileViewItemState = TileViewItemState.Normal;
}
}
}
public static void SetSelectedExchangeID(DependencyObject depObj, String exchangeID)
{
depObj.SetValue(SelectedExchangeIDProperty, exchangeID);
}
public static String GetSelectedExchangeID(DependencyObject depObj)
{
return (String)depObj.GetValue(SelectedExchangeIDProperty);
}
And in ViewModel:
String _selectedExchangeID;
public String SelectedExchangeID
{
get { return this._selectedExchangeID; }
set
{
if (value == null)
{
this.ClearPanels();
this._selectedExchangeID = value;
}
else
{
this._selectedExchangeID = value;
PanelViewModel curPanelViewModel = this.GetPanelViewModel(this._selectedExchangeID);
if (curPanelViewModel != null)
{
curPanelViewModel.Activate(); // this will add to the observable collection for Treeview ItemsSource
}
}
this.OnPropertyChanged("SelectedExchangeID");
}
}
You can do that by doing the processing/heavy loading task asynchronously on a background thread and syncing with foreground thread using UI Dispatcher object only when everything is available and processed.
For details on BackgroundWorker refer to MSDN.
Please note BackgroundWorker is not the only way to do async task. you may opt for Tasks (introduced in .net 4.0) or BeginInvoke/EndInvoke.
And When you are done with Heavy task you may sync with foreground thread in the following way.
First initialize dispatcher on UI thread (lets say Views Constructor):
Dispatcher _UIDispatcher;
public MyView
{
...
_UIDispatcher = Dispatcher.CurrentDispatcher;
}
Then sync in after loading is complete:
public void SyncPostLoading(IEnumerable<Something> myData)
{
_UIDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, System.Threading.ThreadStart)delegate()
{
foreach(Something something in myData)
myObervableCollection.Add(something);
});
}
You have a couple of different options for how to do you work on a background thread. You can use the backgroundworker (slightly outdated) or the .NET 4.0 Tasks (part of the Task Parallel Library). You need to decide if you want to load all of the data into a new collection and invoke an update onto the GUI thread all at once or if you want to load the items in batches and invoke those batches onto the GUI in several rounds