<GridView
Name="Slider"
ItemsSource="{Binding Node.Contents}"
Loaded="SliderLoaded"
.../>
The ViewModel initialization is async and that´s where I set the Node property referenced in the binding.
This is the style for the items panel:
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
On code behind:
private async void SliderLoaded(object sender, RoutedEventArgs e)
{
// I get a null reference exception trying to access the GridView scrollViewer here
await Task.Delay(150); // [hack] wait a bit for the view tree and binding to be ready
// the scrollViewer is accessible after the delay
}
And this how I access the ScrollViewer:
public static ScrollViewer GetScrollViewer(this DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result != null)
return result;
}
return null;
}
Reading docs and other SO answers, the "loaded" event handler seams like the place where all sub-views and bindings are created and available, but as you can see in the comments, it´s not working.
I also tried DataContextChanged event handler with the same result.
How or when can I be 100% sure the grid scrollViewer and items are in place?
You didn't specify how you are getting the GridView's ScrollViewer. I'm assuming you're using VisualTreeHelper to do so?
This works for me:
<GridView Loaded="GridView_Loaded"/>
private void GridView_Loaded(object sender, RoutedEventArgs e)
{
var scrollViewer = ((UIElement)sender)
.ChildrenBreadthFirst()
.OfType<ScrollViewer>()
.First();
}
public static class Extensions
{
public static IEnumerable<UIElement> ChildrenBreadthFirst(this UIElement element)
{
var queue = new Queue<UIElement>();
queue.Enqueue(element);
while (queue.Count > 0)
{
element = queue.Dequeue();
var count = VisualTreeHelper.GetChildrenCount(element);
for (var i = 0; i < count; i++)
{
var child = (UIElement)VisualTreeHelper.GetChild(element, i);
yield return child;
queue.Enqueue(child);
}
}
}
}
Sorry for the confusion. My code was working and I didn´t realize. Thing is that I had a function getting the ScrollView and other stuff to find items and do an animated scrolling. The ScrollView was there but other parts in the code wrapped in a TaskCompletionSource were failing silently.
Anyway, the whole point of all of this was to programmatically scroll the GridView, and all the problems were gone when I replaced the ItemsStackPanel with a StackPanel in the ItemsPanelTemplate.
Sorry to bother and thank you all.
Related
I've been busy experimenting with UWP and WPF. After some getting used to, sometimes stupid, quirks, I decided to make one of my signature... "way overscoped" projects in WPF.
Anyway I'm making an application where I need to bind properties in a static class to UI elements (and when the properties change the UI elements need to change too). I know bindings exist but I've been trying for ages to get the UI to update when the property changes (with INotifyPropertyChanged and the PropertyChanged eventhandler). Eventually, i gave up and decided to make my own binding system(kinda anyway... I've got expansions planned, which is why i want it to be custom).
working of the code:
[Design Time]
Basically, what i have to do is make a property in the VNClient class, add a BindingAttribute(string bindingName) to it and set the Tag of the UI element i want to bind it to to the bindingName. I've got that setup.
[Runtime (only once at startup)]
Now the code will get all properties from the VNClient class with a BindingAttribute and add them to a dictionary as keys, then it will recursively look through the XAML hierarchy and any element with a tag that is also in the dictionary (meaning its bindable) will be added as a value to the dictionary.
[Runtime (every time a property changes)]
An event is fired telling the BindingManager which property changed. It will then get that property name from the dictionary (along with a dependency property but that's not implemented yet) to see which UI elements are bound to that property, then it will change the correct property to the correct value.
Here is the BindingManager:
internal class BindingManager
{
Dictionary<string, List<FrameworkElement>> staticReferenceBindings = new();
public BindingManager()
{
VNClient.PropertyChanged += VNClient_PropertyChanged;
MainWindow.ApplicationLoaded += MainWindow_ApplicationLoaded;
}
private void MainWindow_ApplicationLoaded(object? sender, EventArgs e)
{
foreach (PropertyInfo property in typeof(VNClient).GetProperties())
{
BindingAttribute attr;
if ((attr = (BindingAttribute)property.GetCustomAttribute(typeof(BindingAttribute), false)) != null)
{
staticReferenceBindings.Add(property.Name, null);
}
}
FindBindings(VNClient.MainWindowInstance);
}
private async void VNClient_PropertyChanged(object? sender, (string bindTag, DependencyProperty bindProperty, dynamic value) e)
{
foreach (KeyValuePair<string, List<FrameworkElement>> Binding in staticReferenceBindings)
{
if (Binding.Value == null) continue;
foreach (FrameworkElement element in Binding.Value)
{
DependencyProperty modifiedProperty = e.bindProperty;
//Property conversion for different elements... like background property => fill property
if (Binding is Shape && e.bindProperty == Control.BackgroundProperty) modifiedProperty = Shape.FillProperty;
else if (Binding is Window && e.bindProperty == TextBlock.TextProperty) modifiedProperty = Window.TitleProperty;
if (modifiedProperty != null) element.SetValue(modifiedProperty, e.value);
}
}
}
internal void FindBindings(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
DependencyObject dpObject = VisualTreeHelper.GetChild(parent, i);
FrameworkElement child = dpObject as FrameworkElement;
if (child != null)
{
string childTag = child.Tag?.ToString();
if (childTag != null && staticReferenceBindings.ContainsKey(childTag))
{
if (staticReferenceBindings[childTag] == null) staticReferenceBindings[childTag] = new List<FrameworkElement>();
staticReferenceBindings[childTag].Add(child);
}
}
FindBindings(dpObject);
}
}
}
Here is an example property:
internal static event EventHandler<(string bindTag, DependencyProperty bindProperty, dynamic value)> PropertyChanged;
private static string _gameName = "*Insert name here :)*";
[BindingAttribute(nameof(GameName))]
public static string GameName
{
get
{
return _gameName;
}
set
{
if (_gameName != value) _gameName = value;
OnPropertyChanged(nameof(GameName), TextBlock.TextProperty, value);
}
}
private static void OnPropertyChanged(string bindTag, DependencyProperty bindProperty, dynamic value) => PropertyChanged?.Invoke(Application.Current, (bindTag, bindProperty, value));
And here is that property bound to a TextBlock:
<TabItem Height="60" Width="250" BorderThickness="1" Background="Transparent" BorderBrush="Black" Foreground="White">
<TabItem.Header>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image HorizontalAlignment="Left" Source="/Res/info_96px.png" Margin="0,0,180,0"/>
<TextBlock Text="About" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" TextTrimming="None" Foreground="#BFFFFFFF"/>
</Grid>
</TabItem.Header>
<StackPanel>
<Image Source="/Res/Logo.png" HorizontalAlignment="Center" VerticalAlignment="Top" Height="150" Width="150"/>
<TextBlock Text="Made with *Insert name here :)*" HorizontalAlignment="Center"/>
<WrapPanel HorizontalAlignment="Center">
<!-- EXAMPLE BINDING --><TextBlock Text="{x:Static local:VNClient.GameName}" Tag="GameName" Margin="0,30,5,0" HorizontalAlignment="Center"/>
<TextBlock Text="was made with *Insert name here :)* version:" Margin="0,30,0,0" HorizontalAlignment="Center"/>
<TextBlock Text="{x:Static local:VNClient.EngineVersion}" Tag="EngineVersion" Margin="5,30,0,0" HorizontalAlignment="Center"/>
</WrapPanel>
</StackPanel>
</TabItem>
(static binding is so i can see the binding in the VS editor)
Okay, so, everything works fine BUT when this XAML element is in a TabItem my recursive search can only find the
<TabItem.Header/>
content NOT the
<TabItem.Content/>
meaning the bindings won't update... which is kinda not good...
If anyone has any idea besides "Just use the normal bindings..." that would be amazing
Thanks in advance :)
(and sorry if this is hard to read i am dyslexic)
EDIT:
I got it working by explicitly specifying, if it's a TabItem start another recursive search through it's content before continuing with it's header (it's a bandaid solution for sure, but I have yet to find bugs or similar problems with other controls like tab items)
[search result before]
Not all bindings found
[search result after]
As far as I can tell, all bindings found
[Modified recursive method in BindingManager]
internal void FindBindings(DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childCount; i++)
{
DependencyObject dpObject = VisualTreeHelper.GetChild(parent, i);
FrameworkElement child = dpObject as FrameworkElement;
if (child != null)
{
string childTag = child.Tag?.ToString();
if (childTag != null && staticReferenceBindings.ContainsKey(childTag))
{
if (staticReferenceBindings[childTag] == null) staticReferenceBindings[childTag] = new List<FrameworkElement>();
staticReferenceBindings[childTag].Add(child);
}
}
//New condition here
if (child is TabItem && ((TabItem)child).Content != null)
{
DependencyObject tabContent = ((TabItem)child).Content as DependencyObject;
FindBindings(tabContent);
}
FindBindings(dpObject);
}
}
if anyone still as anything to add to this or a more universal solution pls don't hesitate to comment.
I have a DataTemplate in Page.Resources representing the structure of Tables and their associated number in a Restaurant:
<DataTemplate x:Key="Table">
<RelativePanel x:Name="container" Height="40" Width="40">
<TextBlock x:Name="number" RelativePanel.AlignHorizontalCenterWithPanel="True" RelativePanel.AlignVerticalCenterWithPanel="True"/>
</RelativePanel>
</DataTemplate>
In XAML I use ContentControl because I have to repeat the same structure avoiding copy-paste:
<ContentControl ContentTemplate="{StaticResource Table}" Canvas.Top="200" Canvas.Left="100"/>
<ContentControl ContentTemplate="{StaticResource Table}" Canvas.Top="150" Canvas.Left="300"/>
Then I have a Button to add new ContentControl(Table) dinamically in code-behind
private void NewTable(object sender, RoutedEventArgs e)
{
ContentControl tavolo = new ContentControl();
tavolo.ContentTemplate = (DataTemplate)this.Resources["Table"];
//here i Want to take the textblock called "number" and change his text.
Canvas.SetTop(tavolo, 150);
Canvas.SetLeft(tavolo, 150);
sala.Children.Add(tavolo);
}
But if I want to change the text of textblock called "number" in this function(only for that new Table, not for all the other ContentControl), what should i do? What is the resolution of this problem using databinding?
Moreover, what is the best way to resolve the problem of "copy-paste"? Should I use other structures like Usercontrols?
I hope that Someone will reply to all theese questions. I'm really confused and stucked
How to get Children Objects of a DataTemplate through the use of ContentControl
I found you add the ContentControl dynamically in code-behind. For this scenario, the better way is use VisualTreeHelper to find the child.
public static DependencyObject MyFindChildByName(DependencyObject parant, string ControlName)
{
int count = VisualTreeHelper.GetChildrenCount(parant);
for (int i = 0; i < count; i++)
{
var MyChild = VisualTreeHelper.GetChild(parant, i);
if (MyChild is FrameworkElement && ((FrameworkElement)MyChild).Name == ControlName)
return MyChild;
var FindResult = MyFindChildByName(MyChild, ControlName);
if (FindResult != null)
return FindResult;
}
return null;
}
Usage
private void AppBar_Tapped(object sender, TappedRoutedEventArgs e)
{
ContentControl tavolo = new ContentControl();
tavolo.ContentTemplate = (DataTemplate)this.Resources["Table"];
//here i Want to take the textblock called "number" and change his text.
Canvas.SetTop(tavolo, 150);
Canvas.SetLeft(tavolo, 150);
tavolo.Loaded += Tavolo_Loaded;
sala.Children.Add(tavolo);
}
private void Tavolo_Loaded(object sender, RoutedEventArgs e)
{
var textblcok = MyFindChildByName(sender as ContentControl, "number") as TextBlock;
textblcok.Text = "New Content";
}
I followed this link Detect when WPF listview scrollbar is at the bottom?
But ScrollBar.Scroll doesn't exist for ListView in Windows 10 .. how to achieve this requirement in windows 10
Thanks
Use the methods described here How can I detect reaching the end of a ScrollViewer item (Windows 8)?
If you want incremental loading there's a built-in solution for that. Otherwise go and traverse the visual tree for the scrollviewer object in the template.
You can do it by simply detecting VerticalOffset and ScrollableHeight of your ScrollViewer. Here is my simple code.
XAML:
<!--Apply ScrollViwer over the ListView to detect scrolling-->
<ScrollViewer Name="ContentCommentScroll" Grid.Row="2"
ViewChanged="ContentCommentScroll_ViewChanged" ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<!--My Dynamic ListView-->
<ListView Name="MyDataList" ItemsSource="{Binding MyList}"
ItemTemplate="{StaticResource MyDataTemplate}"
ItemContainerStyle="{StaticResource myStyle}"/>
</ScrollViewer>
And code in its XAML.CS:
// Hold ScrollViwer
public ScrollViewer listScrollviewer = new ScrollViewer();
// Binded event, which will trigger on scrolling of ScrollViewer
private void ContentCommentScroll_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
Scrolling(ContentCommentScroll);
}
private void Scrolling(DependencyObject depObj)
{
ScrollViewer myScroll= GetScrollViewer(depObj);
// Detecting if ScrollViewer is fully vertically scrolled or not
if (myScroll.VerticalOffset == myScroll.ScrollableHeight)
{
// ListView reached at of its end, when you are scrolling it vertically.
// Do your work here here
}
}
public static ScrollViewer GetScrollViewer(DependencyObject depObj)
{
if (depObj is ScrollViewer) return depObj as ScrollViewer;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetScrollViewer(child);
if (result != null) return result;
}
return null;
}
You can also apply similar logic to detect that if ListView Horizontally reached to its end.
You can wrap it with a ScrollViewer and name it. Then you can use some other methods.
My item class has string name and bool picked. I use a class with a static ObservableCollection with static methods to maintain the list. This is all working.
I cannot access the checkbox within the listbox item. I've tried multiple ideas I've found on Stack Overflow and elsewhere. I have tried so many things, it would be too long to mention them all.
This is the latest attempt.
It works when I leave the checkbox out of the code.
I understand I'm failing to access the checkbox, it's not being recognized as part of the list item.
But I just don't know how to fix it.
<ListBox x:Name="ListBox1" ItemsSource="{Binding}"
DoubleTapped="ListBox1_DoubleTapped" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="checkBox" Checked="CheckBox_Checked"
IsChecked="{Binding Picked}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public sealed partial class MainPage : Page
{
Binding myBinding = new Binding();
public MainPage()
{
//....stuff
ListBox1.DataContext = MyList.list;
//... etc
// This works!!
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Update listbox after item has been added and
// user has returned to main page.
ListBox1.UpdateLayout();
}
// This works if I leave the checkbox out of it!!
private void ListBox1_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
CheckBox checkBox = (CheckBox)this.FindName("checkBox");
// Find the index;
int i = ListBox1.SelectedIndex;
// Some stuff..
// This is what is bound to the checkbox in the xaml!!
item.Picked = true;
try
{
//Manually trying to change the checked of the checkbox!!
// Yes increasingly desperate!!
checkBox.IsChecked = true;
}// Necessary as the checkbox is always throwing this.
catch (NullReferenceException e) { }
}
// Alter Picked value to true on checkbox selection.
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
// Find the index;
//THE INDEX IS ALWAYS 1!!!
int i = ListBox1.SelectedIndex;
try
{
//Trying again to manually manipulate, even though
//the data is supposed to be bound.
item.Picked = true;
}
catch (NullReferenceException ex){}
}
I've tried to only include essential information.
I've left out margins, colors etc and basic declarations.. etc to try and reduce the code.
access a named control inside a XAML DataTemplate
I thought I'd provide an answer to elaborate on the accepted answer with the link that solved this for me.
Basically this link provides a good way to loop through the xaml hierarchy using a visual tree, to find controls within the ListBox. So my xaml looks like this:
<ListBox x:Name="ListBox1" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel ...>
<CheckBox x:Name="checkBox" ... />
<TextBlock ... />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
// To select list item and change Picked value to true.
private void ListBox1_DoubleTapped(object sender, Windows.UI.Xaml.Input.DoubleTappedRoutedEventArgs e)
{
// Find the index;
int i = ListBox1.SelectedIndex;
CheckBox checkBox = getCheckBox(ListBox1);
try
{
// Change Picked bool value.
item.Picked = true;
// Check CheckBox to show item selected.
checkBox.IsChecked = true;
}
catch (NullReferenceException exc) { }
}
// Taken and modified from Jerry Nixon. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
// Find the checkBox for that ListBox item.
CheckBox getCheckBox(ListBox ListBox1)
{
var _ListBoxItem = ListBox1.SelectedItem;
var _Container = ListBox1.ItemContainerGenerator.ContainerFromItem(_ListBoxItem);
var _Children = AllChildren(_Container);
var _Name = "checkBox";
var _Control = (CheckBox)_Children.First(c => c.Name == _Name);
return _Control;
}
// Taken and modified from Jerry Nixon. http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html
// Get any child controls from ListItem Container.
// Using a visual tree to access elements on the page
// within the xaml heirarchy of nested elements/tags.
public List<Control> AllChildren(DependencyObject parent)
{
var _List = new List<Control> { };
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var _Child = VisualTreeHelper.GetChild(parent, i);
if (_Child is Control)
_List.Add(_Child as Control);
_List.AddRange(AllChildren(_Child));
}
return _List;
}
Basically this can be used for other events and controls. Useful code to have.
How can I access a Canvas control, stored in DataTemplate of GridView items from my C# code?
<DataTemplate x:Key="250x250ItemTemplate">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Canvas x:Name="Canv"/> <------ I WANT ACCESS THIS CANVAS FROM C# CODE
</Border>
</Grid>
</DataTemplate>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<GridView x:Name="GridViewData" ItemTemplate="{StaticResource 250x250ItemTemplate}"/>
</Grid>
I'm filling GridViewData items from C# code, setting GridViewData.ItemsSource with data from remotely loaded XML.
Then I need to modify Canvas (by adding children to it) of each element separately.
But I don't understand how can I do that.
Can anyone help me with it?
Thank you in advance!
Everyone who interested in answering this question!
I've found a solution here: http://www.wiredprairie.us/blog/index.php/archives/1730
It's horrible that I don't understand why we need to do so much magic here, but it works.
namespace Extension
{
public static class FrameworkElementExtensions
{
public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)
{
if (element == null || string.IsNullOrWhiteSpace(name))
{
return null;
}
if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))
{
return element;
}
var childCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childCount; i++)
{
var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);
if (result != null)
{
return result;
}
}
return null;
}
}
}
and
for (int i = 0; i<GridViewTickers.Items.Count; i++)
{
var element = GridViewTickers.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (element != null)
{
var tb = element.FindDescendantByName("Canv") as Canvas;
if (tb != null)
{
TextBlock tb1 = new TextBlock();
tb1.Text = "hello";
tb.Children.Add(tb1);
}
}
}
If anyone can explain for me what we're doing in this bunch of code - please do this, 'cause my brain is exploding right now :)
Thank you all!