Say I have XAML like
<TabControl Grid.Row="1" Grid.Column="2" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabTitle}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<local:UserControl1 Text="{Binding Text}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I want to ask where does the TabTitle and Text properties come from? I think the should come from each item of Tabs right? Say Tabs is a ObservableCollection<TabViewModel> TabTitle & Text should be from TabViewModel properties right. But it seems true to a certain extend. TabTitle is populated correctly while Text is not.
Text is declared as a Dependency Property in UserControl1 as follows
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""));
When I have tabs not bound to a ObservableCollection<TabViewModel> bindings works fine
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Tab 1">
<local:UserControl1 Text="Hello" />
</TabItem>
<TabItem Header="Tab 2">
<local:UserControl1 Text="World" />
</TabItem>
</TabControl>
If you are binging UserControl to the property from code-behind file, you should use
{Binding RelativeSource={RelativeSource Mode=Self}, Path=Text}
Direct binding as u have works only for DataContext
I think I know what is the problem. The element in the UserControl1, which should be filled by Text property, doesn't observe the changes of this property. So there is two ways:
1) Use PropertyChangedCallback:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""),
new PropertyChangedCallback(OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
((UserControl1)sender).OnTextChanged();
}
private void OnTextChanged()
{
this.myTextBlock.Text = this.Text;
}
2)Tricky binding:
<UserControl x:Class="UserControl1" x:Name="root" ...>
...
<TextBlock Text="{Binding Text, ElementName=root}"/>
...
</UserControl>
Related
I have a ContentControl where I am setting its content to a DataTemplate. I am setting the Tag value of the ContentControl. Is there a way to access this Tag Element in the Data Template and pass it as CommandParameter. In other words I am trying to pass the Tag as a parameter to the DataTemplate. Please help.
<DataTemplate x:Key="SensorStatusControlTemplate" x:DataType="viewModel:SensorBufferState">
<Grid>
<Rectangle x:Name="SensorRectangle"
Fill="{x:Bind Converter={StaticResource SensorStateOverflowConverter},
ConverterParameter={What do I say here to get the Tag}}"
Height="30"
Width="125" />
<TextBlock x:Name="SensorTextBlock"
Text="{x:Bind Converter={StaticResource SensorStateOverflowConverter}}"
FontSize="{StaticResource FontSizeMedium}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White" />
</Grid>
</DataTemplate>
Here is my ControlTemplate. Is there a way to access the Tag in the DataTemplate?
<ContentControl Content="{Binding VmPRWControlData.OverflowSensorState,UpdateSourceTrigger=PropertyChanged}"
ContentTemplate="{StaticResource SensorStatusControlTemplate}"
Tag="Overflow"
HorizontalAlignment="Center"
Width="{Binding ElementName=LABLidSensorTextBlock,Path=ActualWidth}" />
Edit: I have tried doing like this but the parameter value is null,
ConverterParameter={Binding Tag, RelativeSource={RelativeSource Mode=TemplatedParent}}
You should traverse the tree to find the parent control using RelativeSource.AncestorType:
<DataTemplate DataType="{x:Type viewModel:SensorBufferState}">
<Button CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=Tag}"/>
</DataTemplate>
As you correctly mentioned UWP doesn't support RelativeSource.AncestorType.
The following solutions work with WPF too:
Solution 1
You can use Binding.ElementName instead
App.xaml
<DataTemplate x:Key="DataTemplate">
<Button CommandParameter="{Binding ElementName=ContentControl, Path=Tag}"/>
</DataTemplate>
MainPage.xaml
<ContentControl x:Name="ContentControl"
Tag="123"
ContentTemplate="{StaticResource DataTemplate}" />
Solution 2
Or alternatively use the DataContext set to a view model or a DependencyProperty instead of the Tag property:
App.xaml
<DataTemplate x:Key="DataTemplate">
<Button CommandParameter="{Binding CommandParameterValue}"/>
</DataTemplate>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public static readonly DependencyProperty CommandParameterValueProperty = DependencyProperty.Register(
"CommandParameterValue",
typeof(string),
typeof(MainPage),
new PropertyMetadata(default(string)));
public string CommandParameterValue
{
get => (string) GetValue(MainPage.CommandParameterValueProperty);
set => SetValue(MainPage.CommandParameterValueProperty, value);
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
this.CommandParameterValue = "ABC";
}
}
MainPage.xaml
<ContentControl ContentTemplate="{StaticResource DataTemplate}" />
Firstly I see the type for Header and content for the expander, it says the type is object. I have a user control with name CommonExpanderUserControl as follows,
xaml:
<uwpControls:Expander Header="{Binding HeaderContent}" Content="{Binding MainContent}">
</uwpControls:Expander>
In xaml.cs (DataContext is set to this)
public static readonly DependencyProperty HeaderContentProperty =
DependencyProperty.Register("HeaderContent", typeof(object), typeof(CommonExpanderUserControl), new
PropertyMetadata(null));
public object HeaderContent
{
get { return (object)GetValue(HeaderContentProperty); }
set { SetValue(HeaderContentProperty, value); }
}
public static readonly DependencyProperty MainContentProperty =
DependencyProperty.Register("MainContent", typeof(ContentControl), typeof(CommonExpanderUserControl), new
PropertyMetadata(null));
public ContentControl MainContent
{
get { return (ContentControl)GetValue(MainContentProperty); }
set { SetValue(MainContentProperty, value); }
}
Now I am using this UserControl somewhere outside as follows,
<UserControl.Resources>
<ContentControl x:Key="Header">
<Grid x:Name="ExpanderHeaderGrid" HorizontalAlignment="Stretch" Padding="0" Margin="0"
Background="{Binding LisSharedSettings.ChangeHeaderColor,Converter={StaticResource BoolToSolidBrushConverter}}">
<TextBlock x:Name="TextBlockLisSharedSettingsTitle"
x:Uid="/Application.GlobalizationLibrary/Resources/InstrumentSettingsViewLisSettingsTextBlockTitle"
Style="{StaticResource TextBlockStyleSectionHeader}"/>
</Grid>
</ContentControl>
<ContentControl x:Key="Body">
Some content here.
</ContentControl>
</UserControl.Resources>
<Grid>
<local:CommonExpanderUserControl HeaderContent="{StaticResource Header}" MainContent="{StaticResource Body}"/>
</Grid>
Binding Content control like that simply doesn't work. If I remove the MainContent binding and bind only the Header, it says object reference not set to an instance of an object. Please help.
The problem occurs StaticResource binding, we could not bind header with control by StaticResource. And for the scenario the better way is bind HeaderTemplate and send the data source to the header property like the following.
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<Grid
x:Name="ExpanderHeaderGrid"
Margin="0"
Padding="0"
HorizontalAlignment="Stretch"
Background="Red"
>
<TextBlock x:Name="TextBlockLisSharedSettingsTitle" Text="{Binding}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid>
<uwpControls:Expander Header="hello" HeaderTemplate="{StaticResource HeaderTemplate}" />
</Grid>
When we bind textblock.Text with the length of text of Textbox like this
<TextBox x:Name="txtName" Grid.Row="0" />
<TextBlock Text="{Binding ElementName=txtName, Path=Text.Length}" Grid.Row="1" />
The Text of Textblock will change with the text of txtName in real time.
But when I define a new DependencyProperty in a WPF user control like this:
//MyCustomUC.xaml.cs
static FrameworkPropertyMetadata propertymetadata = new FrameworkPropertyMetadata("Comes as Default", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(MyCustom_PropertyChanged), new CoerceValueCallback(MyCustom_CoerceValue), false, UpdateSourceTrigger.PropertyChanged);
public static readonly DependencyProperty MyCustomProperty = DependencyProperty.Register("MyCustom", typeof(string), typeof(MyCustomUC), propertymetadata, new ValidateValueCallback(MyCustom_Validate));
public string MyCustom
{
get
{
return this.GetValue(MyCustomProperty) as string;
}
set
{
this.SetValue(MyCustomProperty, value);
}
}
and bind it to a textbox
//MyCustomUC.xaml
<UserControl ... x:Name="ucs" ...>
<TextBox Text="{Binding ElementName=ucs, Path=MyCustom}"></TextBox>
</UserControl>
//MainWindow.xaml
<local:MyCustomUC x:Name="ucust" Grid.Row="0" />
<TextBox x:Name="tbChange" Text="{Binding ElementName=ucust, Path=MyCustom}" Grid.Row="1"/>
it seems that "MyCustom" will not be changed until Textbox loses focus. How can I make it be changed when the text in Textbox in real time?
Thank you in advance.
<TextBox x:Name="tbChange" Text="{Binding ElementName=ucust, Path=MyCustom, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1"/>
I've defined a class name TextColumns.cs, which has a DependencyProperty RichTextBlockContentProperty:
public static readonly DependencyProperty RichTextBlockContentProperty =
DependencyProperty.Register("RichTextBlockContent", typeof(string),
typeof(RichTextColumns), new PropertyMetadata(""));
public string RichTextBlockContent
{
get { return (string)GetValue(RichTextBlockContentProperty); }
set //Debug, but the SetValue won't fire
{
SetValue(RichTextBlockContentProperty, value);
}
}
In the XAML, I use it as
<FlipView x:Name="flipView"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}">
<FlipView.ItemTemplate>
<DataTemplate x:Name="myDataTemplate">
<UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
<ScrollViewer x:Name="scrollViewer" Style="{StaticResource HorizontalScrollViewerStyle}" Grid.Row="1">
<!-- Content is allowed to flow across as many columns as needed -->
<common:RichTextColumns x:Name="richTextColumns" Margin="117,0,117,47"
RichTextBlockContent="{Binding title}">
<RichTextBlock x:Name="richTextBlock" Width="560" Style="{StaticResource ItemRichTextStyle}">
<Paragraph>
<Run x:Name="RunText" FontSize="26" FontWeight="SemiBold" Text="{Binding title}"/>
</Paragraph>
</RichTextBlock>
</common:RichTextColumns>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
When the page loaded, it's supposed that the RichTextBlockContent will get the value of the Binding "title", while the Binding in the RichTextBlock worked.
Is there something I've missed?
The setter won't get called. If you need to do logic when the value gets set you need to supply a PropertyChanged callback in the PropertyMetadata Constructor
http://msdn.microsoft.com/en-us/library/ms557330.aspx
Solved it myself. It was the way I initialised the Settings collection. Specifying a default when registering it as a DependencyProperty causes all of the Settings to refer to the same collection object. Adding a constructor to Category and explicitly initialising Settings resolves the issue.
A class Category specifies a name and a collection of Settings objects.
using System.Collections.ObjectModel;
using System.Windows;
namespace CasEdit
{
public class Categories : ObservableCollection<Category> { }
public class Category : DependencyObject
{
public string Caption
{
get { return (string)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
public static readonly DependencyProperty CategoryProperty =
DependencyProperty.Register("Caption", typeof(string), typeof(Category),
new UIPropertyMetadata("Category name not set"));
public Settings Settings
{
get { return (Settings)GetValue(SettingsProperty); }
}
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(new Settings()));
}
}
The following XAML defines templates, UI and some test data.
<Window x:Class="CasEdit.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CasEdit="clr-namespace:CasEdit"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type CasEdit:Category}" ItemsSource="{Binding Settings}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Caption}" />
<Button Content="Gratuitous button" Margin="3" Click="Button_Click"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type CasEdit:Setting}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Caption}" Margin="3" />
<TextBlock Text="{Binding Template}" Margin="3" />
<TextBox Text="{Binding Editor}" Margin="3" />
<TextBlock Text="{Binding CasDevice}" Margin="3" />
</StackPanel>
</DataTemplate>
<CasEdit:Categories x:Key="cats">
<CasEdit:Category Caption="1st category">
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 1-1" />
<CasEdit:Setting Caption="Setting 1-2" />
</CasEdit:Category.Settings>
</CasEdit:Category>
<CasEdit:Category Caption="2nd category" >
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 2-1" />
</CasEdit:Category.Settings>
</CasEdit:Category>
<CasEdit:Category Caption="3rd category" >
<CasEdit:Category.Settings>
<CasEdit:Setting Caption="Setting 3-1" />
</CasEdit:Category.Settings>
</CasEdit:Category>
</CasEdit:Categories>
</Window.Resources>
<Grid>
<TreeView x:Name="tree" ItemsSource="{Binding Source={StaticResource cats}}" />
</Grid>
</Window>
You would expect a tree like this
1st category
Setting 1-1
Setting 1-2
2nd Category
Setting 2-1
3rd category
Setting 3-1
but what I get is this
which is very confusing. Where have I gone astray, that each category shows all of the settings?
The last parameter here is telling making it so that every instance of Category has it's Settings property initialized to point to the same object:
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(new Settings()));
Instead, do this:
public static readonly DependencyProperty SettingsProperty =
DependencyProperty.Register("Settings", typeof(Settings), typeof(Category),
new UIPropertyMetadata(null));
public Category()
{
Settings = new Settings();
}