I am transfering FrameworkElement from StackPanel on WPF Page to programmatically created StackPanel. At a certain point I need to refresh bindings and get property values.
At this point FrameworkElement.DataContext has correct value and BindingExpression.Status==BindingStatus.Unattached for all binding expressions. I execute BindingExpression.UpdateTarget, but property values are empty.
I found source code for BindingExpression.UpdateTarget():
public override void UpdateTarget()
{
if (Status == BindingStatus.Detached)
throw new InvalidOperationException(SR.Get(SRID.BindingExpressionIsDetached));
if (Worker != null)
{
Worker.RefreshValue(); // calls TransferValue
}
}
Worker gets instance in internal override void BindingExpression.Activate(). Also inside Activate() BindingExpression.Status set to BindingStatus.Active.
How can I programmatically initiate execution for BindingExpression.Activate() and make UpdateTarget after that?
Update
Solution here (thanks, Olli):
public void UpdateAllBindingTargets( DependencyObject obj)
{
FrameworkElement visualBlock = obj as FrameworkElement;
if (visualBlock==null)
return;
if (visualBlock.DataContext==null)
return;
Object objDataContext = visualBlock.DataContext;
IEnumerable<KeyValuePair<DependencyProperty, BindingExpression>> allElementBinding = GetAllBindings(obj);
foreach (KeyValuePair<DependencyProperty, BindingExpression> bindingInfo in allElementBinding)
{
BindingOperations.ClearBinding(obj, bindingInfo.Key);
Binding myBinding = new Binding(bindingInfo.Value.ParentBinding.Path.Path);
myBinding.Source = objDataContext;
visualBlock.SetBinding(bindingInfo.Key, myBinding);
BindingOperations.GetBindingExpression(visualBlock, bindingInfo.Key).UpdateTarget();
}
}
where getting all bindings for object:
public IEnumerable<KeyValuePair<DependencyProperty, BindingExpression>> GetAllBindings( DependencyObject obj)
{
var stack = new Stack<DependencyObject>();
stack.Push(obj);
while (stack.Count > 0)
{
var cur = stack.Pop();
var lve = cur.GetLocalValueEnumerator();
while (lve.MoveNext())
if (BindingOperations.IsDataBound(cur, lve.Current.Property))
{
KeyValuePair<DependencyProperty,BindingExpression> result=new KeyValuePair<DependencyProperty, BindingExpression>(lve.Current.Property,lve.Current.Value as BindingExpression);
yield return result;
}
int count = VisualTreeHelper.GetChildrenCount(cur);
for (int i = 0; i < count; ++i)
{
var child = VisualTreeHelper.GetChild(cur, i);
if (child is FrameworkElement)
stack.Push(child);
}
}
}
You can programmatically create a new binding, which should do the trick.
http://msdn.microsoft.com/en-us/library/ms752347.aspx#creating_a_binding
Example from the MSDN page
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);
Related
Is there any way to determine (and if possible programmatically set) which groups are collapsed and which are not in a list view. Here's how the listview grouping is set up:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(LvMslInfoTable.ItemsSource);
PropertyGroupDescription groupDescription = new PropertyGroupDescription("GroupObject");
view.GroupDescriptions.Add(groupDescription);
view.SortDescriptions.Add(new SortDescription("GroupObjectSortOrder", ListSortDirection.Ascending));
It took me a while to get to work back at that project. The Link from #Oleg was indeed helpful in that it pointed to the Expander control. The only thing missing was to get into the visual tree and find that element. From then on it was relatively easy to find the missing parts. Here is the code I used in the end. The GetDescendantByType method was copied from somewhere here in Stackoverflow as well. The rest was storing the expanded state in conjunction with a group name (expandedStateStatus is a Dictionary containing those entries).
StackPanel sp = (StackPanel)GetDescendantByType(LvMslInfoTable, typeof(StackPanel));
foreach (var gi in sp.Children.Cast<GroupItem>())
{
var tb = (TextBlock)GetDescendantByType(gi, typeof(TextBlock));
if (tb == null) continue;
Expander exp = (Expander)GetDescendantByType(gi, typeof(Expander));
if (exp == null) continue;
if (!expandedStateStatus.ContainsKey(tb.Text))
{
expandedStateStatus.Add(tb.Text, exp.IsExpanded);
}
}
private 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 frameworkElement)
{
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;
}
I have methods which manages datagridview object:
internal static void LoadChannelsInGrid(DataGridView dg, Label noDataLbl, string feedUrl)
{
var response = RssManager.GetRss(feedUrl);
if (response != null)
{
noDataLbl.Visible = false;
dg.Visible = true;
var items = response.OrderByDescending(s => s.PubDateUnix);
dg.DataSource = items.ToArray();
FontifyDataGrid(dg);
}
else
{
noDataLbl.Visible = true;
dg.Visible = false;
}
}
and
private static void FontifyDataGrid(DataGridView dg)
{
for (var i = 0; i < dg.Rows.Count; i++)
{
var item = dg.Rows[i].DataBoundItem as ChannelData;
if (item == null)
{
continue;
}
if (!item.IsLoaded)
{
var actualFont = new Font("Microsoft Sans Serif", 7.8f, FontStyle.Bold);
dg.Rows[i].DefaultCellStyle.Font = actualFont;
}
}
}
and I call :
LoadChannelsInGrid(dataGridView1, noDataLbl, "https://....");
Seems that rows (which model item satisfy IsLoaded value) don't have bold style, still looks regular.
Why ?
If I understood correctly, you need the font to be bold when the IsLoaded property is true.
In that case you need to update your if (!item.IsLoaded) to if (item.IsLoaded)
inside a behavior (connected to windows.interactivity) im trying to find a child of a Listview - specifically Gridviewcolumn- to be able to resize its width, the code is built using MVVM without code-behind.
the problem is simply that using the code below the Gridviewcolumn is not found, meanwhile other children of the Maingrid , f.e. a Button, it would be no problem (see the button example below).
any suggestions where the mistake comes from? thanks in advance!
// _parent is the main Grid of the Window
Button button = GetTemplateChildByName(_parent); // for the button its working and the value is returned
GridViewColumn gridviewcolumn = GetTemplateChildByNames(_parent);// for the GridViewColumn its not working and no value is returned
....
public Button GetTemplateChildByName(DependencyObject parent)
{
int childnum = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childnum; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is Button)
{
return child as Button;
}
else
{
var s = GetTemplateChildByName(child);
if (s != null)
return s;
}
}
return null;
}
public GridViewColumn GetTemplateChildByNames(DependencyObject parent)
{
int childnum = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childnum; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is GridViewColumn)
{
return child as GridViewColumn;
}
else
{
var s = GetTemplateChildByNames(child);
if (s != null)
return s;
}
}
return null;
}
In my code,
protected override void OnNavigatedTo(NavigationEventArgs e)
{
using (bus_noContext ctx = new bus_noContext(bus_noContext.ConnectionString))
{
ctx.CreateIfNotExists();
ctx.LogDebug = true;
var buses = from c in ctx.Bus_routes
select new bus_list{ BUS_NO = c.BUS_NO, SOURCE = c.SOURCE, DESTINATION = c.DESTINATION};
busno_list.ItemsSource = buses.ToList();
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string temp;
TextBlock nameBox;
ListBoxItem currentSelectedListBoxItem;
for(int i=0;i<busno_list.Items.Count;i++)
{
currentSelectedListBoxItem = this.busno_list.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem;
nameBox = FindDescendant<TextBlock>(currentSelectedListBoxItem);
temp = nameBox.Text;
if (temp.Contains(searchbox.Text))
{
busno_list.SelectedIndex = i;
busno_list.ScrollIntoView(busno_list.SelectedItem);
return;
}
}
}
and in FindDescendant function ,
private T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
// Check if this object is the specified type
if (obj is T)
return obj as T;
// Check for children
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
if (childrenCount < 1)
return null;
// First check all the children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
return child as T;
}
// Then check the childrens children
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = FindDescendant<T>(VisualTreeHelper.GetChild(obj, i));
if (child != null && child is T)
return child as T;
}
return null;
}
But at the line
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
debugger is always throwing InvalidOperationException (Reference is not a valid visual DependencyObject) and in Autos watch tool, obj is showing null value, even if there are already 557 rows in listbox.
So I am not able to correct this exception.
And i find this method best for searching through the listbox having datatemplate as given on How to access a specific item in a Listbox with DataTemplate?. Please suggest where i am going wrong or if there is a better alternative.
Not sure why you're trying to do this by finding the generated element, when you have the source, i.e. something like:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
using (bus_noContext ctx = new bus_noContext(bus_noContext.ConnectionString))
{
ctx.CreateIfNotExists();
ctx.LogDebug = true;
var buses = from c in ctx.Bus_routes
select new bus_list{ BUS_NO = c.BUS_NO, SOURCE = c.SOURCE, DESTINATION = c.DESTINATION};
busno_list.ItemsSource = buses.ToList();
searchbox.Text = buses[20].SOURCE; // or whatever
}
}
I need to access the nodes of a TreeView as a plain list (as if all the nodes where expanded) to be able to do multiselection pressing the Shift key. Is there a way to accomplish this?
Thanks
Here is a method that will retrieve all the TreeViewItems in a TreeView. Please be aware that this is an extremely expensive method to run, as it will have to expand all TreeViewItems nodes and perform an updateLayout each time. As the TreeViewItems are only created when expanding the parent node, there is no other way to do that.
If you only need the list of the nodes that are already opened, you can remove the code that expand them, it will then be much cheaper.
Maybe you should try to find another way to manage multiselection. Having said that, here is the method :
public static List<TreeViewItem> FindTreeViewItems(this Visual #this)
{
if (#this == null)
return null;
var result = new List<TreeViewItem>();
var frameworkElement = #this as FrameworkElement;
if (frameworkElement != null)
{
frameworkElement.ApplyTemplate();
}
Visual child = null;
for (int i = 0, count = VisualTreeHelper.GetChildrenCount(#this); i < count; i++)
{
child = VisualTreeHelper.GetChild(#this, i) as Visual;
var treeViewItem = child as TreeViewItem;
if (treeViewItem != null)
{
result.Add(treeViewItem);
if (!treeViewItem.IsExpanded)
{
treeViewItem.IsExpanded = true;
treeViewItem.UpdateLayout();
}
}
foreach (var childTreeViewItem in FindTreeViewItems(child))
{
result.Add(childTreeViewItem);
}
}
return result;
}
Here is what you asked;
private static TreeViewItem[] getTreeViewItems(TreeView treeView)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
for (int x = 0; x < treeView.Items.Count; x++)
{
returnItems.AddRange(getTreeViewItems((TreeViewItem)treeView.Items[x]));
}
return returnItems.ToArray();
}
private static TreeViewItem[] getTreeViewItems(TreeViewItem currentTreeViewItem)
{
List<TreeViewItem> returnItems = new List<TreeViewItem>();
returnItems.Add(currentTreeViewItem);
for (int x = 0; x < currentTreeViewItem.Items.Count; x++)
{
returnItems.AddRange(getTreeViewItems((TreeViewItem)currentTreeViewItem.Items[x]));
}
return returnItems.ToArray();
}
Call with your control as the first parameter e.g.;
getTreeViewItems(treeView1);