UpdateSourceTrigger don't work in WPF CustomControls - c#

I'm working with WPF and C# on a WPF application in which the controls and their bindings are created at runtime in code-behind.
The WPF-window in which i use the controls has a ViewModel with a DataTable as DataContext and - at the bottom - a DataGrid which is bound to the DefaultView of the DataTable.
At first for creating controls at runtime i used the standard WPF-controls, p.e. the TextBox and the CheckBox.
In their bindings i set UpdateSourceTrigger to "PropertyChanged" like this:
Binding controlBinding = new Binding();
controlBinding.Source = ViewModelContainer.viewmodel.ApplicationDataSet.Tables[BindingSource].DefaultView;
controlBinding.Path = new PropertyPath("[0][" + BindingPath + "]");
controlBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
When i changed the text of the TextBox (without leaving it) or checked/unchecked the CheckBox i saw these changes all at once in the DataGrid.
But now i'm using CustomControls which inherit from the standard controls and the UpdateSourceTrigger-functionality doesn't work anymore.
When i change the text of the TextBox or check/uncheck the CheckBox i see no changes in the DataGrid.
I think that i have to do something in the definitions of my CustomControls, but what?
Here the definitions of the CustomTextBox and the CustomCheckBox:
<!--Style for the CustomControl CustomTextBox-->
<Style TargetType="{x:Type local:CustomTextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBox Text="{TemplateBinding Text}"
TextWrapping="Wrap"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
ContextMenu="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:CustomTextBox}},
Path=ContextMenu}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Style for the CustomControl CustomCheckBox-->
<Style TargetType="{x:Type local:CustomCheckBox}" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomCheckBox}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<CheckBox IsChecked="{TemplateBinding IsChecked}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<TextBlock Text="{TemplateBinding Text}"
TextWrapping="Wrap"
TextAlignment="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CheckBox}},
Path=HorizontalContentAlignment, Converter={StaticResource h2tAlignmentConverter}}"
TextDecorations="{TemplateBinding TextDecorations}"/>
</CheckBox>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Thanks in advance!

Thank You bidy!
I tried it and that's what i was looking for.
I have to guess, that i'm working with CustomControls since just three weeks and have not much experience.
Have you tried <TextBox Text={Binding Text, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged} ... ? – bidy

Related

strange behavior of TemplateBinding

I am trying to create a custom control. After setting the cornerRadius property to be bound it works only for the default radius which is "8". Here is the definition of property:
public CornerRadius cornerRadius
{
get { return (CornerRadius)GetValue(cornerRadiusProperty); }
set { SetValue(cornerRadiusProperty, value); }
}
public static readonly DependencyProperty cornerRadiusProperty =
DependencyProperty.Register("cornerRadius", typeof(CornerRadius), typeof(expandMenuA), new PropertyMetadata(new CornerRadius(8)));
In the main border, everything works fine, but for the second it works only for default "8" when I change a value in different project, which has a reference to this custom control, nothing happens, and it stays the default:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:expandMenuA">
<Style TargetType="{x:Type local:expandMenuA}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:expandMenuA}">
<Border CornerRadius="{TemplateBinding cornerRadius}" x:Name="mainBorder" Background="#232323">
<StackPanel>
<Button Height="{TemplateBinding expanderHeight}" Background="#2d2d2d">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{TemplateBinding local:expandMenuA.cornerRadius}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Button.Template>
<Button.Content>
<TextBlock Text="{TemplateBinding menuTitle}" Foreground="White" Margin="10, 0, 0, 0" Background="{TemplateBinding BorderBrush}"/>
</Button.Content>
</Button>
<ContentPresenter/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
TemplateBinding in ControlTemplate for Button will seek Button's property, which will resolve to default DP value. use Binding with RelativeSource:
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding Path=cornerRadius, RelativeSource={RelativeSource AncestorType={x:Type local:expandMenuA}}}" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>

ItemContainerGenerator.ContainerFromItem() returns null while VirtualizingStackPanel.IsVirtualizing="False"

I'm facing a similar problem with this question however VirtualizingStackPanel.IsVirtualizing="False" didn't solve my problem. Is there anyone facing the same issue?
The thing is I have a custom combobox,
<Style TargetType="{x:Type MultiSelectionComboBox}" >
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
VerticalAlignment="Center"
HorizontalAlignment="Center"
VirtualizingStackPanel.IsVirtualizing="False"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" x:Name="ItemStack" VirtualizingStackPanel.IsVirtualizing="False">
<CheckBox x:Name="CheckBoxItem"
Command="{Binding SelectItem, RelativeSource={RelativeSource AncestorType={x:Type MultiSelectionComboBox}}}"
CommandParameter="{Binding Key}"
>
</CheckBox>
<TextBlock Text="{Binding DisplayText}"></TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid x:Name="Placement" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Black">
<TextBox IsReadOnly="True" Grid.Column="0"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type MultiSelectionComboBox}}}">
</TextBox>
</Border>
<Popup x:Name="PART_Popup"
Grid.Column="0"
Focusable="False"
Grid.ColumnSpan="2"
IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Placement="Bottom"
VerticalOffset="-1"
PlacementTarget="{Binding ElementName=LayoutRoot}">
<Popup.Resources>
<Style TargetType="{x:Type ScrollBar}" BasedOn="{StaticResource {x:Type ScrollBar}}">
<Style.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="BorderThickness" Value="0"/>
</Trigger>
</Style.Triggers>
</Style>
</Popup.Resources>
<ScrollViewer x:Name="DropDownScrollViewer"
Background="{StaticResource Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
MinWidth="{Binding ActualWidth, ElementName=LayoutRoot}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained"/>
</ScrollViewer>
</Popup>
<ToggleButton IsEnabled="{Binding IsEnabled, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type MultiSelectionComboBox}}}" Grid.Column="1" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ComboBoxToggleButton}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
and yet I can't get a reference to the checkbox inside via,
this.ItemContainerGenerator.ContainerFromItem(this.Items[0]) as ComboBoxItem;
Is there any suggestions?
What i actually want to achieve is,
i want to change checkboxes ischecked property which is depending on an other object which can change on runtime. I can't do it with using bindings due to the current state of the overall project which i can not change at this point. So basically once the new MultiSelectionComboBox is created i want to do something like this,
foreach (object item in this.Items)
{
ComboBoxItem comboBoxItem = this.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
if (comboBoxItem == null)
continue;
FrameworkElement element = comboBoxItem.ContentTemplate.LoadContent() as FrameworkElement;
CheckBox checkBox = element.FindName("CheckBoxItem") as CheckBox;
checkBox.IsChecked = this.SelectedItem.Contains(item);
}
try execute UpdateLayout() before this.ItemContainerGenerator.ContainerFromItem(item)
Use ItemContainerGenerator.StatusChanged event from you ComboBox like this:
myComboBox.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
void ItemContainerGenerator_StatusChanged(object sender, System.EventArgs e)
{
if (myComboBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
foreach (var item in myComboBox.Items)
{
var container = (ComboBoxItem)LanguageComboBox.ItemContainerGenerator.ContainerFromItem(item);
}
}
}
As my logic was in the SelectionChanged event, i wondered why the ItemContainerGenerator.ContainerFromItem method always returned null even if the Listbox.SelectedItem was not null and even more strange, Virtualisation was turned off! Looking at the ItemContainerGenerator.Status i saw that it was Primitives.GeneratorStatus.NotStarted then i added a simple test on ItemContainerGenerator.Status == Primitives.GeneratorStatus.ContainersGenerated and finally solved it that way and no need to subsribe to the Status_Changed event.

Click in TextBox Part of ComboBox

We have a Style for ComboBox like:
<Style x:Key="OurComboBox" TargetType="ComboBox">
<!-- omitted style Properties -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton Name="ToggleButton"
Grid.Column="2"
ClickMode="Press"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ComboBoxToggleButton}" />
<ContentPresenter Name="ContentSite"
Margin="3,3,23,3"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
IsHitTestVisible="False" />
<TextBox x:Name="PART_EditableTextBox"
Margin="5,3,23,1"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="Transparent"
Focusable="False"
FontFamily="Arial Narrow"
FontWeight="DemiBold"
Foreground="#FF404040"
IsReadOnly="True"
PreviewMouseDown=""
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
Text="{TemplateBinding Text}"
Visibility="Hidden" />
<!-- omitted PopUp and ControlTemplate.Triggers -->
And based on that, we have another more specific style
<Style x:Key="comboBoxSpecialPage"
BasedOn="{StaticResource OurComboBox}"
TargetType="ComboBox">
<Style.Triggers>
<Trigger Property="SelectedIndex" Value="-1">
<Setter Property="Text" Value="Select value" />
</Trigger>
</Style.Triggers>
</Style>
Which leads to the text "Select value" if nothing is selected in the ComboBox, e.g. on start of the application.
But when I click directly on the TextBox text, nothing happens.
So the question is:
How to achieve that the PopUp is opened, as it does when the rest of the
ComboBox (the part without Text) is clicked?
-edit-
If I omited interesting parts, please let me know, I will add them then.
Perhaps IsHitTestVisible property is what you are looking for, more info here:
Textbox tag and IsHitTestVisible property
ComboBox has a property called DropDownStyle.
Set this to DropDownList and the text area is no longer editable, i.e you have to select from the list.

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.

Create a horizontal line between ListBoxItems (horizontally oriented ListBox)

I want to create a listbox like this:
-----|-----|-----|-----|-----|-----
The |'s are my listboxitems which I have separated using margins. This works fine. What I want is the listbox to have a background that contains this line. Or at least have it in the background. I tried a separator but that is not what I want because that is also clickable since I used it in the itemtemplate.
Any ideas?
Thanks
You have to add a style with a control template to the ListBox items.
<Style x:Key="{x:Type ListBoxItem}" TargetType="ListBoxItem">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border"
BorderThickness="1,0"
BorderBrush="Black"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The Border control is intended for this type of decorative lines. The side's thickness are independently set, so use just one side for a single vertical line. On a related note, have you tried using a Grid control for your layout instead of margins?
I'm trying to create a timeline like control. I now fixed it using this:
<ListBox.Template>
<ControlTemplate>
<Border BorderThickness="2" BorderBrush="Black">
<Grid>
<Rectangle Height="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" Fill="Black"/>
<ScrollViewer Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</ListBox.Template>
This works fine in a testproject, but I'm having some problems in my main project. The rectangle remains under the scrollviewer. Well... making progress. Hope to fix this thing soon.
Thanks for your answers so far!

Categories

Resources