I have a uwp project that i have a booking function in and would be pleased to have some help with one thing.
So i'm trying to reach a Rectangle in my UserControl from my Main.xaml.cs file, so i can set the Rectangles fill property.
This is how i have done it:
Method in Main.xaml.cs for finding the child property:
private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null) return null;
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return child;
}
else
{
// Not found it - search children
DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
return nextLevel;
}
}
return null;
}
Inside the function where i want to call the Rectangle that is named StatusColor I do this:
Rectangle Rec = FindChildControl<Rectangle>(GridView1, "StatusColor") as Rectangle;
But somehow when I try to execute the code I get this error for the variable Rec:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
I know there are many posts about this exception but i still not getting any smarter by looking at them...
But as i said, I would be very pleased with someones help
Thanks in advance!
EDIT:
My xaml in Main.xaml to declare the UserControl:
<GridView x:Name="GridView1" ItemContainerStyle="{StaticResource testgrid}" ItemsSource="{x:Bind Rooms}" Width="1740" Height="835" IsHitTestVisible="False" ScrollViewer.HorizontalScrollBarVisibility="Hidden" Margin="75,190,75,100" FontFamily="Segoe MDL2 Assets" IsDoubleTapEnabled="False" IsHoldingEnabled="False" IsRightTapEnabled="False" IsTapEnabled="False">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:Room">
<local:TemplateGrid x:Name="TemplateGrid"/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
And this is how my code looks like for the Rectangle:
<Rectangle x:Name="StatusColor" x:FieldModifier="public" Margin="0,0,0,0" Height="218" VerticalAlignment="Top">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#00000000" Offset="0.901"/>
<GradientStop Color="#CC17FF00" Offset="0"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
It is these lines:
// Not a framework element or is null
if (fe == null) return null;
You are exiting the loop too early. When the element i is not a framework element, you should just ignore it and continue on to the next one.
So instead of exiting when you encounter a non-framework element, just execute the next lines only when it is one:
if (fe != null)
{
// rest of checking
}
or alternatively:
if (fe == null) continue;
EDIT
A slight reworking of your code to:
private T FindChildControl<T>(DependencyObject control, string ctrlName)
where T: FrameworkElement
{
int childNumber = VisualTreeHelper.GetChildrenCount(control);
for (int i = 0; i < childNumber; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(control, i);
FrameworkElement fe = child as FrameworkElement;
// Not a framework element or is null
if (fe == null)
{
continue;
}
if (child is T && fe.Name == ctrlName)
{
// Found the control so return
return (T)child;
}
else
{
// Not found it - search children
T nextLevel = this.FindChildControl<T>(child, ctrlName);
if (nextLevel != null)
{
return nextLevel;
}
}
}
return null;
}
should be called with
Rectangle rec = FindChildControl<Rectangle>(GridView1, "StatusColor");
to give the correct result. At least it did in my test situation (with adjusted parameters).
The only real change was the use of continue instead of return. Furthermore I made use of generics to immediately return the correct type, no casting needed.
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 ListView that displays a table with various columns. Each cell of a row in the table contains a different type of control; I am trying to allow the user to edit the data in each row by selecting a row and double-clicking it to make the cells editable. So I have been able to get all of them to work with the exception of the column that contains ComboBoxes.
XAML code:
This is the XAML code for the ListView. It has about 7 columns but I am focusing on the column with ComboBoxes as depicted here.
<ListView x:Name="MyListView" IsSynchronizedWithCurrentItem="True" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,12,0,0" Height="315" Width="560" ItemsSource="{Binding People}">
<ListView.View>
<GridView>
<!-- More Grid column code here -->
<GridViewColumn Header="Fleet" Width="70">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Name="locationCmboBx" ItemsSource="{Binding DataContext.SchoolLocations, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}" Loaded="OnCmboBxLoad" IsEnabled="False" Width="55" HorizontalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- More Grid column code here -->
</GridView>
</ListView.View>
</ListView>
C# code:
So here in the code-behind I am trying to use the VisualTreeHelper as recommended by others to get access to the locationsCmboBx (ComboBox) nested inside of the DataTemplate, CellTemplate and other XAML headers in the ListView.
// More code before here
ListView listViewItem = (ListView)(MyListView.ItemContainerGenerator.ContainerFromItem(MyListView));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(listViewItem);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
ComboBox comboBox = (ComboBox)myDataTemplate.FindName("locationsCmboBx", myContentPresenter);
// More code before here
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
So everything I have works but when I debug through the code and get to the FindName function ComboBox is null. Ultimately, I want to set the IsEnabled property on it and get the SelectedValue from the locationsCmboBx. I believe I am missing something but not sure of what. Any help would be appreciated?
The problem in your code is a typo by combo box's name in XAML locationCmboBx and in code behind is locationsCmboBx.
The code:
ListView listViewItem = (ListView)(MyListView.ItemContainerGenerator.ContainerFromItem(MyListView));
is also wrong. Argument of ContainerFromItem() must be a data item. Returned type is also wrong. It must be ListViewItem
I would recommend you to use a ViewModel + bindings and not a code behind to access the data. So you can avoid such a tipo errors. See also: Detect in XAML broken bindings already at compile time
I found this information:
public static class ListViewHelper
{
public static FrameworkElement GetElementFromCellTemplate(ListView listView, Int32 column, Int32 row, String name)
{
if (row >= listView.Items.Count || row < 0)
{
throw new ArgumentOutOfRangeException('row');
}
GridView gridView = listView.View as GridView;
if (gridView == null) { return null; }
if (column >= gridView.Columns.Count || column < 0)
{
throw new ArgumentOutOfRangeException('column');
}
ListViewItem item = listView.ItemContainerGenerator.ContainerFromItem(listView.Items[row]) as ListViewItem;
if (item != null)
{
GridViewRowPresenter rowPresenter = GetFrameworkElementByName(item);
if (rowPresenter != null)
{
ContentPresenter templatedParent = VisualTreeHelper.GetChild(rowPresenter, column) as ContentPresenter;
DataTemplate dataTemplate = gridView.Columns[column].CellTemplate;
if (dataTemplate != null && templatedParent != null)
{
return dataTemplate.FindName(name, templatedParent) as FrameworkElement;
}
}
}
return null;
}
private static T GetFrameworkElementByName(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElement child = null;
for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
System.Diagnostics.Debug.WriteLine(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
else if (child != null)
{
child = GetFrameworkElementByName(child);
if (child != null && child.GetType() == typeof(T))
{
break;
}
}
}
return child as T;
}
}
Source: How do I access the ui element at a row/cell in my GridView?
<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.
I am wonder if a PopUp can be A LayeredWindow.
I am using Encoder 4 and there is a property that allows the program to not capture layered window.
Here is my code to show the feed in a tooltip
public class MyToolTip : ToolTip
{
protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
{
if (newTemplate != null)
{
this.Visibility = Visibility.Collapsed;
this.IsOpen = true;
Popup popup = GetPopupFromVisualChild(this);
if (popup != null)
{
popup.AllowsTransparency = false;
}
this.IsOpen = false;
this.Visibility = Visibility.Visible;
}
}
private static Popup GetPopupFromVisualChild(Visual child)
{
Visual parent = child;
FrameworkElement visualRoot = null;
while (parent != null)
{
visualRoot = parent as FrameworkElement;
parent = VisualTreeHelper.GetParent(parent) as Visual;
}
Popup popup = null;
if (visualRoot != null)
{
popup = visualRoot.Parent as Popup;
}
//popup.
return popup;
}
}
Wpf :
<Grid>
<Button Width="10" Height="10" Click="Button_Click">
<Button.ToolTip>
<w:MyToolTip Height="500" Width="550" StaysOpen="True">
<WindowsFormsHost x:Name="wrapper" Margin="0,0,0,0" Background="{x:Null}">
<wf:Panel x:Name="previewScreen" BackColor="Transparent" Size="500,500" >
<wf:Panel.Controls>
<wf:Panel x:Name="PreviewScreen2" BackColor="Transparent" Size="500,500"></wf:Panel>
</wf:Panel.Controls>
</wf:Panel>
</WindowsFormsHost>
</w:MyToolTip>
</Button.ToolTip>
</Button>
</Grid>
This issue is Encoder 4, only accept a HandleRef for the preview window.
This is what i DO with Encoder :
MediaSource is a LiveDeviceSource
if (mediaSource != null && mediaSource.PreviewWindow == null)
mediaSource.PreviewWindow =
new PreviewWindow(new HandleRef(PreviewWindow.PreviewScreen2, PreviewWindow.PreviewScreen2.Handle));
What i want is to have a tooltip that shows the webcam preview but not being recorded with the screen.
If I Don't use the Popup, the feed doesn't show. Altought Encoder understand it's a LayeredWindow and doesn't record it.
If I use the popup the feed shows but it's being recorded.
Somehow it's not a layeredWindow anymore even if it's in a tooltip.
Some help would be greatly apprecied, and rewarded =)
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!