bind value to textblock inside resourcedictionary - c#

I have following resourcedictionay.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:samplePrjkt"
>
<ToolBar x:Key="MyToolbar" Height="120">
<!--Template-->
<GroupBox Header="Template" Style="{StaticResource ToolbarGroup}" Margin="3">
<StackPanel Grid.Row="1" Orientation="Horizontal">
<StackPanel Orientation="Vertical" Margin="0,2,0,2">
<TextBlock Text="{Binding TextValue}"></TextBlock>
</StackPanel>
</StackPanel>
</GroupBox>
</ToolBar>
</ResourceDictionary>
that resourcedictionay used in following WPF user-control like follows.
<UserControl x:Class="Sampleprjkt.sample.sampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Sampleprjkt"
>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="29*"/>
<RowDefinition Height="107*"/>
</Grid.RowDefinitions>
<ContentControl Content="{StaticResource MyToolbar}"/>
</Grid>
</UserControl>
I'm trying to bind value to this text block inside the WPF user-control constructor like follows
public partial class SampleWindow : UserControl
{
private string _textValue;
public string TextValue
{
get { return _textValue; }
set
{
_textValue = value;
}
}
public SampleWindow()
{
InitializeComponent();
_textValue = "XXXXX";
}
}
but once I run this, I can see "XXXXX" value not set to <TextBlock Text="{Binding TextValue}"></TextBlock> , what I missed here ?

Your ContentControl is missing a DataContext, which is null. The Binding will always refer to the object in the DataContext and thus the Binding will not find the TextValue.
You could simply set the DataContext of the UserControl to itself:
public SampleWindow()
{
InitializeComponent();
_textValue = "XXXXX";
this.DataContext = this;
}
The DataContext is inherited down to the TextBlock, which will now display the text.

The path of a binding that you define in XAML ("TextValue" in your case) refers to a name of the property of the current DataContext of the element (TextBlock in your case) or the source of the binding.
This means that you should either set the DataContext as suggested by #Sharada Gururaj:
public SampleWindow()
{
InitializeComponent();
_textValue = "XXXXX";
DataContext = this;
}
...or specify an explicit source of the binding:
<TextBlock Text="{Binding Path=TextValue, Source={RelativeSource AncestorType=UserControl}}"></TextBlock>

Related

Data Binding to User Control Error Data.Binding cannot be converted to System.String

I'm new to WPF and I'm having trouble binding text to a user control I made. It's a simple control, it's just basically a button with text and a image that I want to reuse in several Views.
Here is my User Control's .cs
public partial class MenuItemUserControl : UserControl
{
public string TextToDisplay { get; set; }
public MenuItemUserControl()
{
InitializeComponent();
DataContext = this;
}
}
Here is my User Control's xaml
<UserControl x:Class="Class.Controls.MenuItemUserControl"
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:Class.Controls"
mc:Ignorable="d"
d:DesignHeight="83.33" d:DesignWidth="512">
<Grid>
<Button Style="{StaticResource MenuItemStyle}" Height="Auto" Width="Auto">
<DockPanel LastChildFill="True" Width="512" Height="83.33" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Resources/MenuArrow.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock d:Text="sample" Text="{Binding TextToDisplay, Mode=OneWay}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="{StaticResource MenuItems}"/>
</DockPanel>
</Button>
</Grid>
</UserControl>
Here is my View xaml
<Page x:Class="Class.Views.MenuOperate"
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:uc="clr-namespace:Class.Controls"
xmlns:local="clr-namespace:Class.Views"
xmlns:properties="clr-namespace:Class.Properties"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:viewmodels="clr-namespace:Class.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MenuOperateViewModel}"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="1024"
Title="MenuOperate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="512"/>
<ColumnDefinition Width="512"/>
</Grid.ColumnDefinitions>
<uc:MenuItemUserControl TextToDisplay="{Binding StartStop, Mode=TwoWay}" Grid.Row="0" Grid.Column="0"/>
</Grid>
</Page>
Here is my View Model .cs
namespace Class.ViewModels
{
public class MenuOperateViewModel : ObservableObject
{
private string? _StartStop;
public MenuOperateViewModel()
{
StartStop = Properties.Resources.MenuOperateStart;
}
public string? StartStop
{
get => _StartStop;
set => SetProperty(ref _StartStop, value);
}
}
}
This is the error I get in my View Xaml:
Object of Type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'.
There are two things that prevent that the expression
TextToDisplay="{Binding StartStop}"
works.
The target property of the Binding, i.e. TextToDisplay must be a dependency property.
You must not explicity set the UserControl's DataContext. The Binding will resolve the source property path relative to the current DataContext, i.e. with DataContext = this; in the control's constructor, it expects the source property StartStop on the UserControl, which is obviously wrong.
For details, see Data binding overview
Your code should look like this:
public partial class MenuItemUserControl : UserControl
{
public static readonly DependencyProperty TextToDisplayProperty =
DependencyProperty.Register(
nameof(TextToDisplay),
typeof(string),
typeof(MenuItemUserControl));
public string TextToDisplay
{
get { return (string)GetValue(TextToDisplayProperty); }
set { SetValue(TextToDisplayProperty, value); }
}
public MenuItemUserControl()
{
InitializeComponent();
}
}
The Binding in the UserControl's XAML would then use a RelativeSource Binding.
<TextBlock Text="{Binding TextToDisplay,
RelativeSource={RelativeSource AncestorType=UserControl}}" />

Binding property of MainViewModel to SubView

I'm trying to figure out how to bind a property from my MainWindowViewModel to a ContentControl that is based on another View.
RelativeSource Binding seems not to work since the View is in another xaml file?
I tried with dependancy property but I didn't understand how to do this correctly I guess.
What I want to achieve here is that when I type in the TextBox of the MainWindow that all 3 views (contentcontrols) also view the updated data. It should be a demo to illustrate that in MVVM the ViewModel can change without knowledge of the Views and different Views react to it.
Sadly RelativeSource Binding and DependancyProperty didn't work for me or I missed a point.
MainWindow.xaml
<Window x:Class="MVVMDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<TextBlock Text="MVVM Demo" HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"/>
<TextBox Text="{Binding TestString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="200" Background="Black" Foreground="White" Margin="0 20 0 0"/>
<Grid Margin="0 20 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
</Grid.RowDefinitions>
<ContentControl Grid.Column="0" Grid.Row="0" Content="{Binding ViewOne}"/>
<ContentControl Grid.Column="1" Grid.Row="0" Content="{Binding ViewTwo}"/>
<ContentControl Grid.Column="2" Grid.Row="0" Content="{Binding ViewThree}"/>
</Grid>
</StackPanel>
</Window>
MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
/// <summary>
/// String to change the reaction of views
/// </summary>
private string _TestString;
public string TestString
{
get { return _TestString; }
set { _TestString = value; NotifyPropertyChanged(); }
}
private object _ViewOne { get; set; }
public object ViewOne
{
get { return _ViewOne; }
set { _ViewOne = value; NotifyPropertyChanged(); }
}
private object _ViewTwo { get; set; }
public object ViewTwo
{
get { return _ViewTwo; }
set { _ViewTwo = value; NotifyPropertyChanged(); }
}
private object _ViewThree { get; set; }
public object ViewThree
{
get { return _ViewThree; }
set { _ViewThree = value; NotifyPropertyChanged(); }
}
public MainWindowViewModel()
{
ViewOne = new ViewOne();
ViewTwo = new ViewTwo();
ViewThree = new ViewThree();
TestString = "ABC";
}
}
ViewOneViewModel
<UserControl x:Class="MVVMDemo.Views.ViewOne"
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:MVVMDemo.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="White">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=TestString, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Foreground="Black"/>
<TextBlock Text="{Binding TestStringProperty}" Foreground="Black"/>
</StackPanel>
</Grid>
</UserControl>
ViewOneViewModel
public class ViewOneViewModel : ViewModelBase
{
// this doesn't help
public static readonly DependencyProperty TestStringProperty =
DependencyProperty.Register("TestString", typeof(string), typeof(MainWindowViewModel), new PropertyMetadata(null));
}
I believe your issue lies here:
<TextBlock Text="{Binding Path=TestString, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Foreground="Black"/>
Binding Path=TestString should instead be Binding Path=DataContext.TestString as the RelativeSource is looking at the window now, not the window's model.

WPF: ContentTemplate of ContentControl is always null

I have a ContentControl bound to an instance of class X and a DataTemplate for X in the resource section of the Main Window.
<Window x:Class="DT1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DT1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby"/>
</StackPanel>
</Grid>
</Window>
In the button click handler in the MainWindow code behind I have a handler which tries to access the ContentTemplate of the ContentControl but this is always null:
using System.Windows;
namespace DT1
{
public class X
{
}
public partial class MainWindow : Window
{
public X MyX { get; set; } = new X();
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var template = cont.ContentTemplate;
System.Diagnostics.Debug.WriteLine($"ContentTemplate: {template?.ToString() ?? "template is null"}");
}
}
}
What is the right way to access the visual items in the DataTemplate for X?
It is null because you are not setting any datatemplate explicitly. When you define a datatemplate without providing an x:key and providing a DataType using x:Type you are making use of what it is known as Implicit Datatemplates.
In order to get the datatemplate from codebehind you must assign it explicitly:
<Window.Resources>
<DataTemplate DataType="{x:Type local:X}" x:Key="XTemplate">
<TextBlock Text="Hello, World!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<Button Content="Press me" Click="Button_Click" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ContentControl x:Name="cont" Background="Pink" Content="{Binding MyX}" Tag="Blobby" ContentTemplate="{StaticResource XTemplate}"/>
</StackPanel>
</Grid>
Hope this helps!

How to dynamically change DataTemplate according to bound object's type?

I'm trying to create a DataTemplate for a View, to show a specific UserControl type (like a texbox, combobox, custom control or another View) based on the type of object it is bound to.
I have the following MVVM framework:
FieldView is tied to an instance of FieldPresenter, and should display a <Textblock /> for the "Label" property, and a UserControl or another View for the Value (based on the Type of the value), with it's DataSource set to the Value property of the Presenter. Currently, I do not have the second part working. I can't figure out how to write a WPF template for what I need.
ViewModel:
public class FieldPresenter : Observable<object>, IFieldPresenter, INotifyPropertyChanged
{
public FieldPresenter() { }
public FieldPresenter(object value)
{
Value = value;
}
object IFieldPresenter.Value
{
get
{
return base.Value;
}
set
{
base.Value = value;
OnPropertyChanged("Value");
}
}
private string _label;
public virtual string Label
{
get
{
return _label;
}
private set
{
_label = value;
OnPropertyChanged("Label");
}
}
}
View:
<UserControl x:Class="My.Views.FieldView"
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:ViewModels="clr-namespace:My.ViewModels"
mc:Ignorable="d"
d:DesignHeight="24" d:DesignWidth="100">
<UserControl.DataContext>
<ViewModels:FieldPresenter/>
</UserControl.DataContext>
<UserControl.Template>
<ControlTemplate>
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
</Grid.ColumnDefinitions>
<StackPanel Margin="0,0,0,0" HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth}">
<TextBlock Text="{Binding Label}" FontWeight="Bold" Height="32" HorizontalAlignment="Stretch"/>
<TextBox Text="{Binding Value}" Height="Auto" HorizontalAlignment="Stretch"/>
</StackPanel>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
I'm curious if what I'm trying to do is even possible, or if I can workaround it by making my Presenter viewmodel return a UserControl rather than an object value, and have the Presenter parse the UserControl Type from the object type, but I don't feel like my Presenter should be instantiating Controls (or what is technically an unbound view). Should I make an interface, something like IViewAs<controlType> { controlType View { get; } }?
How else would I replace <TextBox Text="{Binding Value}" /> in the above script with some kind of template of a UserControl based on the databound object's type?
You almost certainly want a ContentTemplateSelector :
Code:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Primitive primitive;
primitive = new Sphere();
// primitive = new Cube();
DataContext = primitive;
}
}
internal abstract class Primitive
{
public abstract string Description { get; }
}
internal class Cube : Primitive
{
public override string Description
{
get { return "Cube"; }
}
}
internal class Sphere : Primitive
{
public override string Description
{
get { return "Sphere"; }
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var frameworkElement = container as FrameworkElement;
if (frameworkElement != null && item != null)
{
if (item is Cube)
{
return frameworkElement.FindResource("CubeTemplate") as DataTemplate;
}
if (item is Sphere)
{
return frameworkElement.FindResource("SphereTemplate") as DataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<local:MyTemplateSelector x:Key="myTemplateSelector" />
<DataTemplate x:Key="CubeTemplate" DataType="local:Cube">
<Border BorderBrush="Blue"
BorderThickness="1"
CornerRadius="5" />
</DataTemplate>
<DataTemplate x:Key="SphereTemplate" DataType="local:Sphere">
<Border BorderBrush="Red"
BorderThickness="1"
CornerRadius="50" />
</DataTemplate>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0"
Content="{Binding Description}"
d:DataContext="{d:DesignInstance local:Primitive}" />
<ContentControl Grid.Row="1"
Content="{Binding}"
ContentTemplateSelector="{StaticResource myTemplateSelector}" />
</Grid>
</Window>
Result:
See the documentation for more:
https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx

How to use binding in a TabItem with DataTemplate in WPF?

I'm trying to do something basic (I think!), but I've got a problem. I have got a TabControl and 3 TabItems. The 2 first items must be untouched, and I want to apply a dataTemplate on the third tabItem, so I used a DataTemplateSelector. This is OK, it works. But then, I want to fill data in the third tabItem with my datamodel. Binding is not working because my DataContext is always "null" in the tabItem. How can I set the DataContext in the tabItem created by the dataTemplate ? Here is my code :
XAML :
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="910" Width="1200">
<Window.Resources>
<DataTemplate x:Key="configurationTemplate" DataType="{x:Type local:ConfigurationDatamodel}">
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
</DataTemplate>
<local:TabItemTemplateSelector ConfigurationTemplate="{StaticResource configurationTemplate}" x:Key="tabItemTemplateSelector"/>
</Window.Resources>
<Grid>
<TabControl Height="800" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="tabControl1" VerticalAlignment="Top" ContentTemplateSelector="{StaticResource tabItemTemplateSelector}">
<TabItem Header="TabItem1" Name="tabItem1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem2" Name="tabItem2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<some stuff />
</TabItem>
<TabItem Header="TabItem3" Name="tabItem3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
</TabItem>
</TabControl>
</Grid>
</Window>
UserControl of my DataTemplate :
<UserControl x:Class="WpfApplication1.ConfigurationTemplateUC"
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"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="1000">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Label Content="File name : " Height="28" HorizontalAlignment="Left" Margin="50,43,0,0" Name="label1" VerticalAlignment="Top" FontSize="16"/>
<Label Content="{Binding Path=File}" FontSize="16" Height="28" HorizontalAlignment="Left" Margin="160,43,0,0" Name="label2" VerticalAlignment="Top" Width="324" />
</Grid>
</UserControl>
Datamodel :
public class ConfigurationDatamodel
{
private string file;
public string File
{
get { return this.file; }
set
{
this.file= value;
}
}
public ConfigurationDatamodel()
{}
public ConfigurationDatamodel(string file)
{
this.file= file;
}
}
Code-behind :
public MainWindow()
{
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
InitializeComponent();
tabItem3.DataContext = dt1;
}
There is no binding errors in the console, but the label containing the filename is always empty. The DataContext of the UserControl "ConfigurationTemplateUC" is always "null".
Any thoughts?
EDIT
If I set the DataContext in the contructor of the UserControl, it works :
public ConfigurationTemplateUC()
{
InitializeComponent();
ConfigurationDatamodel dt1 = new ConfigurationDatamodel("example.txt");
this.DataContext = dt1;
}
How can I set this dataContext with the DataTemplate ?
In your DataTemplate, Remove the DataContext binding from
<local:ConfigurationTemplateUC DataContext="{Binding DataContext.ConfigurationDatamodel}" />
...leaving only
<local:ConfigurationTemplateUC />
UI in a DataTemplate automatically gets the bound data as its DataContext, so in your case you override the correct DataTemplate with a path to something that can't be found.
EDIT:
Also, you don't need a DataTemplateSelector to do this. You can remove the selector and instead simply use TabItem.ContentTemplate:
<TabItem Header="TabItem3" Name="tabItem3" ContentTemplate="{StaticResource configurationTemplate}" >
...

Categories

Resources