Custom control including wpf TabControl fails bindings with ElementName - c#

I've a CustomControl which has an Items property. An internal TabControl is bound to it like this:
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TabControl Name="PART_TabControl"
>
</TabControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OnApplyTemplate:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TabControl tabControl = Template.FindName("PART_TabControl", this) as TabControl;
foreach (var item in Items)
{
Dispatcher.BeginInvoke(new Action(() => tabControl.Items.Add(item)), System.Windows.Threading.DispatcherPriority.ContextIdle);
}
}
To demonstrate the problem I've added "Dispatcher.BeginInvoke". The problem occurs when tabitems are added after a while. The usage below results in a binding failure:
<ccl:CustomControl1 Name="cc1">
<ccl:TabItemCollection>
<TabItem Header="Tab1">
<TextBox Name="txtData" />
</TabItem>
<TabItem Header="Tab2">
<TextBlock Text="{Binding ElementName=txtData, Path=Text}" />
</TabItem>
</ccl:TabItemCollection>
</ccl:CustomControl1>
I noticed that after snooping the textblock, the problem is fixed. And from the source code I see this code does the job:
BindingExpression expression = BindingOperations.GetBindingExpression(dObj, property);
if (expression != null && !expression.HasError && expression.Status != BindingStatus.Active)
{
dObj.ClearValue(property);
BindingOperations.SetBinding(dObj, property, expression.ParentBindingBase);
}
As I've many controls in tab items this kind of "fix binding" code causes a lot of performance issues.
Is there any workaround for that?

Related

ItemTemplate Wrapper in UserControl Based on ItemsControl

I am working on my own user control which derives from ItemsControl. To give a brief description of what I want to achieve here is what the XAML looks like right now:
<ItemsControl x:Class="MyApp.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyApp"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="600">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
This presents my data the way I want to, in a horizontal StackPanel, nothing fancy. In the visual tree you'll see this:
ItemsControl
[...Other Visual Item in The Tree...]
ItemsPresenter
ContentPresenter
Item1 (ItemTemplate)
ContentPresenter
Item2 (ItemTemplate)
Now I would like to modify the XAML of my user control so that the tree would look like this:
ItemsControl
[...Other Visual Item in The Tree...]
ItemsPresenter
ContentPresenter
SomeContainerDefinedInMyUserControlXAML
Item1 (ItemTemplate)
ContentPresenter
SomeContainerDefinedInMyUserControlXAML
Item2 (ItemTemplate)
The goal here is to have a wrapper container around the templated item. Its behavior would be bound to internal properties of the UserControl, allowing me to define item behaviors immune to the ItemTemplate choice made by the user of my control.
I was trying to add DataTemplates on ContentPresenter in ItemsControl.Resources, but that failed. Could someone help me out here? :)
You may create a derived ContentPresenter or ContentControl in the ItemControl's Get​Container​For​Item​Override method:
public class MyContainer : ContentControl
{
}
public class MyItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyContainer();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyContainer;
}
}
I'd also suggest to move the derived ItemsControl's XAML to a default Style in Themes\Generic.xaml (as done by default for custom controls):
<Style TargetType="local:MyItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyItemsControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Also add
static MyItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyItemsControl),
new FrameworkPropertyMetadata(typeof(MyItemsControl)));
}
to the control's code.

XamlParseException on extended ListView in Windows Phone 8.1

I've been scouring the internet for a week looking for an answer to my question, but nobody else seems to be hitting this issue.
I am migrating an app from Windows Phone 8 Silverlight to Windows Phone 8.1 WinRT, and am having an issue with a custom control I made. In the Silverlight app, I created a custom LongListSelector that I predefined a ItemTemplate for with custom binding and logic. I reuse this LongListSelector in a few different places in the app.
I am trying to do the same thing in my WinRT app, but with ListView. The issue is that when I try to include my custom extended ListView in any XAML page, I get an E_UNKNOWN_ERROR XamlParseException with the Line and Position number set to the end of the opening tag of my ListView.
Here is what my custom ListView's XAML looks like:
<ListView
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyAppNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="using;System;assembly=mscorlib"
x:Class="MyAppNamespace.CustomListView"
x:Name="This"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
.... my data template is here ...
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And my code-behind is this:
namespace MyAppNamespace
{
public partial class CustomListView: ListView
{
public CustomListView()
{
this.InitializeComponent();
}
... event handlers and custom logic here ...
}
}
And here's how I reference it in another XAML page
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyAppNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Class="MyAppNamespace.SamplePage"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<local:CustomListView DataContext="{Binding Items}"/>
</Grid>
</Page>
The error shows up in Blend and in the design view of the xaml file in Visual Studio. When I run the app and navigate to the page where I am using this control, the error appears on the LoadComponent function call within the generated InitializeComponent.
The weird thing is that if I switch the root element to UserControl and put the ListView inside it (and update the base class in the code-behind), then everything works fine, but I'd rather just directly extend the ListView then wrap it in a UserControl.
Finally figured it out!!
I needed to override the PrepareContainerForItemOverride and then set the Loaded event for the ListViewItem passed in as the first parameter. Then, in the event handler, apply the callbacks as needed by traversing the element tree.
PrepareContainerForItemOverride will be called every time a row of the ExtendedListView is populated with a different element from the ItemsSource, but the Loaded callback will only be called once per row unless the row is unloaded (you can also add a callback handler for this if needed.
Below is some sample code to help out anyone else who has this issue!!
Here's the relevant contents of ExtendedListView.cs:
public sealed class ExtendedListView : ListView
{
public ExtendedListView()
{
this.DefaultStyleKey = typeof(ExtendedListView);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
ListViewItem lvi = element as ListViewItem;
lvi.Loaded += lvi_Loaded;
}
void lvi_Loaded(object sender, RoutedEventArgs e)
{
ListViewItem lvi = sender as ListViewItem;
ApplyCallbacksToElement(lvi.ContentTemplateRoot);
}
private void ApplyCallbacksToElement(DependencyObject element)
{
if (null != element)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(element, i);
// Code for adding element callbacks goes here
//
// For example:
// if (IsButtonAndMatchesCondition(child))
// {
// (child as Button).Click += button_Click;
// }
//
ApplyCallbacksToElement(child);
}
}
}
}
And here's the relevant content of the Generic.xaml file generated in the Themes folder created after making a new TemplatedControl:
<Style TargetType="local:ExtendedListView">
<Setter Property="IsTabStop"
Value="False" />
<Setter Property="TabNavigation"
Value="Once" />
<Setter Property="IsSwipeEnabled"
Value="True" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Auto" />
<Setter Property="ScrollViewer.HorizontalScrollMode"
Value="Disabled" />
<Setter Property="ScrollViewer.IsHorizontalRailEnabled"
Value="False" />
<Setter Property="ScrollViewer.VerticalScrollMode"
Value="Enabled" />
<Setter Property="ScrollViewer.IsVerticalRailEnabled"
Value="False" />
<Setter Property="ScrollViewer.ZoomMode"
Value="Disabled" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled"
Value="False" />
<Setter Property="ScrollViewer.BringIntoViewOnFocusChange"
Value="True" />
<Setter Property="ItemContainerTransitions">
<Setter.Value>
<TransitionCollection>
<AddDeleteThemeTransition />
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<!-- Put your custom DataTemplate here -->
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ExtendedListView">
<Border BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer x:Name="ScrollViewer"
TabNavigation="{TemplateBinding TabNavigation}"
HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}"
VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}"
IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
ZoomMode="{TemplateBinding ScrollViewer.ZoomMode}"
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}"
AutomationProperties.AccessibilityView="Raw">
<ItemsPresenter Header="{TemplateBinding Header}"
HeaderTemplate="{TemplateBinding HeaderTemplate}"
HeaderTransitions="{TemplateBinding HeaderTransitions}"
Footer="{TemplateBinding Footer}"
FooterTemplate="{TemplateBinding FooterTemplate}"
FooterTransitions="{TemplateBinding FooterTransitions}"
Padding="{TemplateBinding Padding}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can only extend controls that inherit from UserControl the way you are trying (Like Page for example).
What you have to do is just create a class that inherits from ListView and modify default style for it.
<Style TargetType="my:CustomListView">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="..">
...

WPF Template.FindName return always null

Template
<Style TargetType="{x:Type local:Viewport}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Viewport}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas x:Name="PART_Canvas" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
And the code in OnApplyTemplate
content = this.Template.FindName("PART_Canvas", this) as FrameworkElement;
the content returns always null, why it doesn't work?
if I replace with this, the program quits directly
content = this.ItemsPanel.FindName("PART_Canvas", this) as FrameworkElement;
With FindName you can find only elements declared in a Template. ItemsPanel is not part of that template. ItemsControl puts ItemsPanel into ItemsPresenter place holder via which you can access your Canvas but first you need to name ItemsPresenter in your template:
<ControlTemplate TargetType="{x:Type local:Viewport}">
<Border>
<ItemsPresenter x:Name="PART_ItemsPresenter"/>
</Border>
</ControlTemplate>
then, using VisualTreeHelper get your Canvas, but I think earliest place when you can call code below is when FrameWorkElement is Loaded. This is my example:
public class MyListBox : ListBox
{
public MyListBox()
{
AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(ControlIsLoaded));
}
private void ControlIsLoaded(object sender, RoutedEventArgs e)
{
var canvas = VisualTreeHelper.GetChild(this.Template.FindName("PART_ItemsPresenter", this) as DependencyObject, 0);
}
}

Changing active tab's background in C# and XAML

OK, I've searched everywhere and in every single link I go to my problem is explained with xaml code.
I want to change the active tab's background and foreground (not its content, but the upper part which you select in order to make active) in a WPF project, but I'm looking for the C# code. The code below doesn't work for me:
if (tabs[0].IsEnabled) tabs[0].Background = Brushes.Blue;
else tabs[0].Background = Brushes.Black;
Do it in XAML if you use WPF.
You can bind to the TabControl's property ItemsSource. Than just define a Styletrigger to change the Background
OK, thanks to Venson I've finally got it and just in case someone wants to know how it works:
<TabControl ItemsSource="{Binding tabs}" Height="68" HorizontalAlignment="Left" Margin="156,23,0,0" Name="tabControl1" VerticalAlignment="Top" Width="268">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Margin="0,0,-4,0"
Background="Black"
BorderBrush="Blue"
BorderThickness="1,1,1,1"
CornerRadius="2,12,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Blue"></Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Margin="0,0,-4,0"
Background="Green"
BorderBrush="Blue"
BorderThickness="1,1,1,1"
CornerRadius="2,12,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
This code goes in the <Grid>of the <Window> tags of the MainWindow.xaml and
public MainWindow()
{
testClass testObject = new testClass();
testObject.tabs = new List<TabItem>();
testObject.tabs.Add(new TabItem());
testObject.tabs.Add(new TabItem());
testObject.tabs[0].Header = "NO WAY";
testObject.tabs[1].Header = "ON WAY";
testObject.tabs[0].Content = "WHAT";
testObject.tabs[1].Content = "HELL";
InitializeComponent();
this.DataContext = testObject ;
}
class testClass
{
public List<TabItem> tabs { set; get; }
}
this goes into the MainWindow.xaml.cs file.
Please note: the colors are only for the test, don't judge me for the bad contrast chosen!
I don't know if it's possible not to use another class, though..
foreach(var tab in tabs)
{
tab.Background = tab.IsEnabled ? Brushes.Blue :Brushes.Black;
}
But you can handle state of tabcontrol on active tab changes and set backgrounds of deactivated and activated tabs.

How to get ContentPresenter in code behind?

I have the code below under Themes\Generic.xaml
Now I need to get cPresenter in code behind how I can do it?
In fact I try to convert Silverlight implementation into WPF code.
And I want to use something like Silverlight has:
FrameworkElement cp = this.GetTemplateChild("cPresenter") as FrameworkElement;
Themes\Generic.xaml
<Style TargetType="local:Marquee">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Marquee">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Width="Auto" Height="Auto" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" VerticalScrollBarVisibility="Disabled">
<Canvas>
<ContentPresenter x:Name="cPresenter" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Canvas>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There's no reason that shouldn't work in WPF also.
Put this in your code for Marquee.
private ContentPresenter cPresenter;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.cPresenter = this.GetTemplateChild("cPresenter") as ContentPresenter;
}

Categories

Resources