Accessing Elements or Controls in a UWP ControlTemplate - c#

I have a XAML UserControl that inherits from the CalendarView element. In the CalendarViewItemStyle, I have edited the ControlTemplate to hold a Grid and a TextBox.
<Style x:Name="CalDayStyle"
TargetType="CalendarViewDayItem">
<Setter Property="Background"
Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="cTemp">
<Grid x:Name="RootGrid"
Background="{TemplateBinding Background}">
<StackPanel x:Name="CalDayStack"
Orientation="Vertical"
VerticalAlignment="Stretch">
<TextBlock x:Name="tasksPres"
TextAlignment="Center"
HorizontalAlignment="Stretch">
</TextBlock>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am trying to access the TextBox but to no avail, using FindName(). What do I do.

FindName cannot find names that are defined in applied templates. To find items in applied templates, use VisualTreeHelper.GetChild to get the applied template root object. Then you can call FindName on that root object, and you will be searching the XAML namescope of the template rather than the greater page.
If you want to get the TextBlock in your Style, you should be able touse the VisualTreeHelper to get it.
For examole:
public MainPage()
{
this.InitializeComponent();
texts = new List<TextBlock>();
}
private List<TextBlock> texts;
private void Button_Click(object sender, RoutedEventArgs e)
{
IEnumerable<TextBlock> textBlocks = FindVisualChildren<TextBlock>(Mycontrol);
foreach (var textBlock in textBlocks)
{
if (textBlock.Name == "tasksPres")
{
texts.Add(textBlock);
}
}
foreach (var item in texts)
{
item.Text = "11111111111";
}
}
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}

Related

Remove ListViewItemPresenter border in Microsoft.UI.Xaml ListView

I'm in the process of converting my UWP app to be a WinUI 3 app. I noticed there's a difference in the way ListViewItems are displayed. The screenshots below show an added Border child to the ListViewItemPresenter in WinUI 3.
UWP
--
WinUI 3
The XAML for both is as simple as it gets:
<ListView>
<ListView.Items>
<ListViewItem>One</ListViewItem>
</ListView.Items>
</ListView>
I'm wondering if there's a way to remove the extra Border element from the WinUI 3 ListViewItemPresenter. I noticed it has a default margin of 4, 2, 4, 2, which causes visual differences relative to my existing UWP app.
I'm not sure if you can remove the Border inside the ListViewItemPresenter but you can modify its Margin like this:
MainPage.xaml
<Grid>
<ListView x:Name="CustomListView">
<ListView.Items>
<ListViewItem>One</ListViewItem>
<ListViewItem>Two</ListViewItem>
<ListViewItem>Three</ListViewItem>
</ListView.Items>
</ListView>
</Grid>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
foreach (ListViewItem listViewItem in this.CustomListView.FindChildrenOfType<ListViewItem>())
{
if (listViewItem
.FindChildrenOfType<ListViewItemPresenter>()
.FirstOrDefault() is ListViewItemPresenter listViewItemPresenter &&
listViewItemPresenter
.FindChildrenOfType<Border>()
.FirstOrDefault() is Border border)
{
border.Margin = new Thickness(0, 0, 0, 0);
}
}
}
}
Extensions.cs
public static class Extensions
{
public static IEnumerable<T> FindChildrenOfType<T>(this DependencyObject parent) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is T childOfT)
{
yield return childOfT;
}
foreach (T grandChild in child.FindChildrenOfType<T>())
{
yield return grandChild;
}
}
if (parent is ContentControl contentControl)
{
if (contentControl.Content is T contentOfT)
{
yield return contentOfT;
}
if (contentControl.Content is DependencyObject dependencyObjectContent)
{
foreach (T grandChild in dependencyObjectContent.FindChildrenOfType<T>())
{
yield return grandChild;
}
}
}
}
This works if you have pre-fixed items in the ListView. If you need to add items after the Loaded event, you need to apply this for the new items.
I'm also struggling with the same issue, I've got one way which might be a little bit nicer depending on your preferences of course. Just get the styles of the listviewitempresenter from the themeresources file https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/CommonStyles/ListViewItem_themeresources.xaml then add a negative margin Margin="-4,-2.4,-4,-2.4" to neutralize the margin of the border. Still not a nice way and still looking for a better ways...
<ItemsControl.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter Margin="-4,-2.4,-4,-2.4" ContentTransitions="{TemplateBinding ContentTransitions}"
Control.IsTemplateFocusTarget="True"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
DragBackground="{ThemeResource ListViewItemDragBackground}"
DragForeground="{ThemeResource ListViewItemDragForeground}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"
SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}"
PressedBackground="{ThemeResource ListViewItemBackgroundPressed}"
SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
ContentMargin="{TemplateBinding Padding}"
CheckMode="{ThemeResource ListViewItemCheckMode}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>

When drag text and On Mouse hover to drop it into TextBox, increase TextBox size and overlap on the other controls

I want to increase the size of a TextBox Control whenever the user drag a node from Treeview control and hovers the mouse over the TextBox.
The size increase should not readjust the other controls, rather the current control should overlap the neighboring controls.
I tried to implement the code WPF: On Mouse hover on a particular control, increase its size and overlap on the other controls
but it doesn't work when hover on TextBox and left mouse button is pressed for dragged text.
<ItemsControl Margin="50">
<ItemsControl.Resources>
<Style x:Key="ScaleStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Grid.ZIndex" Value="1"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="1.1" ScaleY="1.1"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.Resources>
</ItemsControl>
Here is a small sample application. Contrary to my comment, we need the PreviewDragEnter event since the text box already has Drag/Drop support. In Window_Loaded, the application registers the event handlers. Then, in TextBox_PreviewDragEnter, the new style is set manually. We also store the old z-index to allow restoring it in TextBox_PreviewDragLeave.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525" Loaded="Window_Loaded">
<StackPanel Margin="8">
<TextBox/>
<TextBox/>
<TextBox/>
<TextBox/>
</StackPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//From https://stackoverflow.com/a/978352/1210053
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
foreach (var txt in FindVisualChildren<TextBox>(this))
{
txt.PreviewDragEnter += TextBox_PreviewDragEnter;
txt.PreviewDragLeave += TextBox_PreviewDragLeave;
txt.PreviewDrop += TextBox_PreviewDragLeave;
}
}
private Dictionary<TextBox, int> oldZIndex = new Dictionary<TextBox, int>();
private void TextBox_PreviewDragEnter(object sender, DragEventArgs e)
{
var txt = (TextBox)sender;
oldZIndex.Add(txt, Panel.GetZIndex(txt));
Panel.SetZIndex(txt, 1);
var scaleTransform = new ScaleTransform(1.1, 1.1, txt.ActualWidth / 2, txt.ActualHeight / 2);
txt.RenderTransform = scaleTransform;
}
private void TextBox_PreviewDragLeave(object sender, DragEventArgs e)
{
var txt = (TextBox)sender;
txt.RenderTransform = null;
Panel.SetZIndex(txt, oldZIndex[txt]);
oldZIndex.Remove(txt);
}
}
Approach from a different angle. Use the code behind to handle left click and drag.
Pseudo code...
If hover over textbox.text ==true
Textbox size = 300;
Then check the Grid location of the textbox. It should be allowed to columnspan over the other columns, while the rest of the controls stay fixed in their grid.row and grid.column locations.

Validating WPF Visual Tree

I am trying to enforce that any new style added should have to meet some standards. Here is a simple example.
<Style TargetType="{x:Type CellValuePresenter}" BasedOn="{StaticResource {x:Type CellValuePresenter}}">
<Setter Property="Tag" Value="1"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness="{TemplateBinding BorderThickness}" />
....
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Updated:
I am trying to validate the style from code behindthat if the style has ControlTemplate, its root should be Border with BorderThickness template binded.
Since Visual Tree is not created until the Control is rendered, I am creating the control at runtime based on target type and applying the style. And I even tried the following
Appropriate way to force loading of a WPF Visual
I can see the visual tree in WPF Tree Visualizer, but I cannot navigate fully using LogicalTreeHelper.GetChildren
I am just experimenting with ideas around:
private void ValidateStyle(Style fieldStyle_, Field field_)
{
if (fieldStyle_.TargetType == typeof(CellValuePresenter))
{
StringBuilder sb = new StringBuilder();
CellValuePresenter presenter = new CellValuePresenter();
presenter.Style = fieldStyle_;
if (fieldStyle_.Setters.Count > 0)
{
foreach (Setter setter in fieldStyle_.Setters)
{
if (setter.Property != null && setter.Property.PropertyType == typeof(ControlTemplate))
{
presenter.ApplyTemplate();
ValidateColumnStyle(0, presenter);
}
}
}
}
}
private void ValidateColumnStyle(int depth_, object obj_)
{
Debug.WriteLine(new string(' ', depth_) + obj_);
if (!(obj_ is DependencyObject))
{
return;
}
if (obj_ is UIElement)
{
Viewbox vb = new Viewbox() {Child = obj_ as UIElement};
vb.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
vb.Arrange(new Rect(vb.DesiredSize));
}
foreach (object child in LogicalTreeHelper.GetChildren(obj_ as DependencyObject))
ValidateColumnStyle(depth_ + 1, child);
}
I am not able to navigate to Border element, any idea how to properly load the control.

Applying style to elements inside a DataTemplate

I have a UserControl which simply contains a TextBlock and TextBox inside a DataTemplate. This is done in the following way:
<UserControl.Resources>
<DataTemplate DataType="{x:Type Binding:StringBindingData}" x:Key="dataTemp">
<StackPanel Orientation="Horizontal" Name="sPanel">
<TextBlock Name="txtDescription" Text="{Binding Description}" />
<TextBox Name="textboxValue" Text="{Binding Mode=TwoWay, Path=Value, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="textItemsControl" ItemsSource="{Binding}"/>
</Grid>
I need to be able to apply different styles to the TextBlock/TextBox under different circumstances. For example in certain instances I would like to be able to apply a white Foreground to the TextBlock or change the width of a TextBox.
I have tried a few different approaches:
In the window where the control is being used I have set the style for the TextBlock:
<Style TargetType="{x:Type TextBlock}" >
<Setter Property="Foreground" Value="White" />
</Style>
This worked for all other TextBlocks in the window.
I also attempted to get the DataTemplate in the codebehind using
var myDataTemplate = (DataTemplate)this.Resources["dataTemp"];
But was unable to get any further in applying the style to all the TextBlock elements.
I am not sure of your requirement though. But for finding controls from code behind, i would recommend to use VisualTreeHelper. I generally used this helper function to do my stuff -
public IEnumerable<T> FindVisualChildren<T>( DependencyObject depObj )
where T : DependencyObject
{
if( depObj != null )
{
for( int i = 0; i < VisualTreeHelper.GetChildrenCount( depObj ); i++ )
{
DependencyObject child = VisualTreeHelper.GetChild( depObj, i );
if( child != null && child is T )
{
yield return (T)child;
}
foreach( T childOfChild in FindVisualChildren<T>( child ) )
{
yield return childOfChild;
}
}
}
}
Usage:
foreach (var textBlock in FindVisualChildren<TextBlock>(this))
{
/* Your code here */
}

Make select controls visible based on selected tab using xaml

I've got following code:
private Dictionary<int, UserControl> tabControls = new Dictionary<int, UserControl>();
public MainWindow()
{
InitializeComponent();
tabControls[0] = new Panel1();
tabControls[1] = new Panel2();
tabControls[2] = new Panel3();
tabControls[3] = new Panel4();
tabControls[4] = new Panel5();
tabControls[5] = new Panel6();
tabControls[6] = new Panel7();
tabControls[7] = new Panel8();
}
public object SelectedTab
{
//this is assigned from xaml binding
set
{
OnCurrentTabChanged(tabControl.SelectedIndex);
}
}
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
}
Every time the user selects different tab, an other control appears.
Is there any way to simplify this using xaml?
I cannot put the controls themselves inside the tab control
I've done this before with another TabControl which has it's headers and frame hidden. Then I just bind the SelectedIndex to your other tab's SelectedIndex, and the two are synchronized
<!-- TabControl without the TabHeaders -->
<Style x:Key="TabControl_NoHeadersStyle" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can setup your two tab controls, each bound to different sources, and bind the SelectedIndex of one to the SelectedIndex of the other
<TabControl x:Name="MainTabControl" />
<TabControl ItemsSource="{Binding Panels}"
SelectedIndex="{Binding ElementName=MainTabControl, Path=SelectedIndex}"
Style="{StaticResource TabControl_NoHeadersStyle}" />
Another alternative is to bind the SelectedIndex to something in your code-behind, then anytime it changes, raise a PropertyChanged notification on another property that exposes the panel you want to display.
<TabControl SelectedIndex="{Binding SelectedTabIndex} />
<ContentControl Content="{Binding SelectedPanel}" />
and in the code behind
public int SelectedTabIndex
{
get { return _selectedTabIndex;}
set
{
if (_selectedTabIndex != value)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
RaisePropertyChanged("SelectedPanel");
}
}
}
public UserControl SelectedPanel
{
get { return tabControls[SelectedTabIndex]; }
}
TabItem has an IsSelected propery you could bind to that I think would simplify the syntax.
public bool TabIsSelected
{
get { return tabIsSelected; }
set
{
if (value && dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
tabIsSelected = value;
}
But I still don't get why you can't just put the control in the tabitem?
using codebehind
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
UIElemnt[] pp = dataDisplay.Children.Cast<UIElement>().ToArray();
Array.ForEach(pp, x=> x.visibility = Visibility.Collapsed);
pp[tabIndex].visibility = Visibility.Visible;
}
}

Categories