I want to bind an element in a page to dependency property in code behind with compiled binding and same time bind another element to ViewModel with usual binding. But it gives a runtime error.
Here is my xaml code.
<Page
x:Class="XbindingProblem.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XbindingProblem"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
DataContext="{Binding Main, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="UserDataTemplate" x:DataType="local:User">
<StackPanel>
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind Age}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBlock Text="{Binding Title}"/>
<ContentPresenter ContentTemplate="{StaticResource UserDataTemplate}" Content="{x:Bind CurrentUser, Mode=OneWay}"/>
</StackPanel>
</Grid>
Here CurrentUser is dependency property which is initially null and then change in run time. This gives following runtime error.
Incorrect type passed into template. Based on the x:DataType global::XbindingProblem.User was expected.
The problem is it passes the ViewModel into UserDataTemplate instead of CurrentUser dependency property when CurrentUser is null.
Can anyone have a good explanation on this problem?
If you remove DataContext="{Binding Main, Source={StaticResource Locator}}", it will work. Why? because {x:Bind CurrentUser} is looking for a property called CurrentUser sitting inside your MainPage.xaml.cs. Since the CurrentUser is indeed a dependency property of your page, it will just work.
However, by specifying the DataContext of your page, the x:Bind is now excepting a CurrentUser property inside your MainViewModel instance, and of course it's not going to find it, so a compile-time error will be thrown.
One possible fix is to set the this.CurrentUser really early, even before calling InitializeComponent.
this.CurrentUser = new User();
InitializeComponent();
But this is IMHO not the the right way of doing things, as it's basically a racing game - it tries to populate the ContentPresenter before the DataContext gets updated, and in the end you will end up having the TextBlock (of which Text binds to Title) and the ContentPresenter attached to different contexts!
So ask yourself why you need to create a dependency property for CurrentUser inside a Page object, instead of having a normal property (with INotifyPropertyChanged implementation) sitting inside your MainViewModel? I'd prefer the latter, 'cause it's more semantically correct.
The question is interesting, what I have done is just remove the datacontext and this is the code behind is similar to yours:
public sealed partial class BlankPage1 : Page
{
public User CurrentUser
{
get { return (User)GetValue(CurrentUserProperty); }
set { SetValue(CurrentUserProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentUser. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentUserProperty =
DependencyProperty.Register("CurrentUser", typeof(User), typeof(BlankPage1), new PropertyMetadata(null));
public BlankPage1()
{
this.InitializeComponent();
CurrentUser = new User() { Name = "Hello", Age = "20" };
}
}
public class User
{
public String Name { get; set; }
public String Age { get; set; }
}
might be you have the User class in another namespace or have another class in the typeof(...) of the dependency property. Because I tested that and works. The DataContext of the page can be whatever you want it won't affect.
Then I added the datacontext just to test:
<Page.DataContext>
<local:Main/>
</Page.DataContext>
and the code just for testing:
public class Main
{
public String Title { get; } = "title";
public User MainUser { get; set; }
}
And it does not throws any exception, appears the Main data and the CurrentUser data.
UPDATE. The error happens when the User is null so it is like the x:Bind is null it propagates to the Binding,To solve that (it was tough):
<Page x:Name="Root"
x:Class="Deletetb.BlankPage1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Deletetb"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" >
<Page.DataContext>
<local:Main/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="UserDataTemplate" x:DataType="local:User">
<StackPanel>
<TextBlock Text="{x:Bind Name}" />
<TextBlock Text="{x:Bind Age}" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel DataContext="{x:Null}">
<TextBlock Text="{Binding DataContext.Title, ElementName=Root}" />
<ContentPresenter ContentTemplate="{StaticResource UserDataTemplate}" Content="{x:Bind CurrentUser, Mode=OneWay}"/>
</StackPanel>
</Grid>
</Page>
Where is binding defined (TextBlock) I set the datacontext to null in the parent container (StackPanel) and bind by element name, and it does not crash, also I added a wait by code to test and set the Current User and it works. That was a challenge. hope it also works for you.
Although it breaks the idea of MVVM a bit, you can add a property to your page like this:
public MainViewModel viewModel => DataContext as MainViewModel;
And then in the XAML code reference the page property
<ContentPresenter Content="{x:Bind viewModel.CurrentUser, Mode=OneWay}" />
Related
I've tried pretty hard to find a solution by myself searching the web and following examples but everything I've tried until now has failed. I know that my poor experience with WPF is making me missing something huge and silly but as a matter of fact I'm stuck.
As written in the object, I have a custom UserControl that contains a RadioButton. I want to 'expose' the Command of the RadioButton outside through a DependencyProperty of my UserControl.
The .xaml of the UserControl (named 'ImageRadioButton') is the following:
<UserControl x:Class="WpfSinergoHMIControls.Controlli.ImageRadioButton"
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="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid>
<Grid>
<RadioButton Command="{Binding SomeCommand, ElementName=me}" Name="button1" Foreground="White">
</RadioButton>
</Grid>
</Grid>
</UserControl>
the dependency property in the UserControl program file is the following:
public static readonly DependencyProperty SomeCommandProperty =
DependencyProperty.Register(
"SomeCommand",
typeof(ICommand),
typeof(ImageRadioButton),
new UIPropertyMetadata(null));
public ICommand SomeCommand
{
get { return (ICommand)GetValue(SomeCommandProperty); }
set { SetValue(SomeCommandProperty, value); }
}
Finally I declare in the application that uses my UserControl an istance:
<Controlli:ImageRadioButton x:Name="btnAutomatic" GroupName="MainMenu" SomeCommand="{Binding DataContext.NavigateAutomaticCommand, ElementName=MainViewObj}" HorizontalAlignment="Left" Height="60" VerticalAlignment="Bottom" Width="140" Canvas.Left="1373" Canvas.Top="5" Margin="6,0,0,5" IsChecked="True"/>
worthless to say that this doesn't work (no command is called). I know that there is something silly that I'm missing but after a lot of trials/searching I still cannot find the solution.
Thanks!
You reference the element me in your command binding, but you do not assign that name anywhere, which means that the binding source (your UserControl) cannot be found at runtime.
Command="{Binding SomeCommand, ElementName=me}"
If you set the name on your UserControl everything works as expected (at least for me).
<UserControl x:Class="WpfSinergoHMIControls.Controlli.ImageRadioButton"
...
x:Name="me">
I'm messing around with WPF and creating User Controls but having a hard time understanding how the databinding is supposed to work. Data binding seems to be overly complex and as long as WPF has been out I would think MS would've created some shortcuts to prevent having to do so much boilerplate code.
User control xaml
<UserControl x:Class="WPFTest.FancyBox"
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">
<DockPanel>
<Label Content="{Binding MyText}"></Label>
</DockPanel>
</UserControl>
User control .cs
public partial class FancyBox : UserControl
{
public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register("MyText", typeof(string), typeof(FancyBox), new PropertyMetadata(null));
public string MyText
{
get => (string)GetValue(MyTextProperty);
set => SetValue(MyTextProperty, value);
}
public FancyBox()
{
InitializeComponent();
}
}
Usage in my main window
<StackPanel>
<local:FancyBox MyText="testing!"/>
</StackPanel>
The binding Content="{Binding MyText}" is binding to the DataContext of the control (Label) which is inherited from closest ancestor up the tree who have one (your code doesn't show any DataContext assignment)
Your intended behavior is for Label's Content to bind to the User Control's Property in this case you need to make the user control your source. Many ways to do this for example:
<UserControl x:Class="WPFTest.FancyBox"
x:Name="RootElement"
....
<Label Content="{Binding MyText, Source={x:Reference RootElement} />
Or another way:
<Label Content="{Binding MyText, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:FancyBox}}" />
Keep in mind any Bindings without a source (Source, RelativeSource) will source from DataContext.
I guess I didn't need data binding for this at all
I changed my controls label to:
<Label x:Name="lblText"></Label>
and my code-behind to:
public string MyText
{
get => lblText.Content.ToString();
set => lblText.Content = value;
}
I have a property in a view model which I would like to be able to set via the XAML but I can't figure out how to do it.
I have a pretty basic user control (containing a list of items), two of which are to be placed on a page and I would like to be able to set one to be a 'Source' (defined by an enum) and one to be a 'Target'.
[The code below has been stripped down quite a bit so apologies if I've accidentally made some mistakes or missed something out.]
My enumeration is:
public enum ConversionSide
{
Source, // Convert something FROM whatever is here.
Target // Convert something TO whatever is here.
}
I have a page which looks like this:
<Page
x:Class="MyApp.Views.ConverterPage"
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:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<my:SelectorPage Name="SourceSelector" Grid.Column="0" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" />
</Grid>
</Page>
...where SelectorPage is a user control (I've called it a 'Page' to make the Prism AutoWire work but that's not the issue here) containing a list of items (all working fine) which looks like this...
<UserControl
x:Class="MyApp.Controls.SelectorPage"
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:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<ListView
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.MyList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.MySelectedItem, Mode=TwoWay}">
<ListView.Header>
<TextBlock Margin="0,8,0,8" HorizontalAlignment="Center" FontStyle="Italic" Text="Header Text" />
</ListView.Header>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MyListItem">
<my:MyListItemTemplate />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
..with code behind as...
public sealed partial class SelectorPage : UserControl
{
private SelectorViewModel ViewModel => DataContext as SelectorViewModel;
public SelectorPage()
{
this.InitializeComponent();
}
}
SelectorViewModel looks like this...
public class SelectorViewModel : ViewModelBase
{
private ConversionSide _side;
public ConversionSide Side
{
get { return _side; }
set { SetProperty(ref _side, value); }
}
// Many lines have been omitted for 'clarity'.
}
I would like to be able to set the Side property of SelectorViewModel in XAML like this...
<my:SelectorPage Name="SourceSelector" Grid.Column="0" Side="Source" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" Side="Target" />
(Once Side has been set, I do not expect it to ever change.)
How can I do this?
I've looked at using a dependency property but I can't get it to change the property in SelectorViewModel. When I add one in SelectorPage it's visible in the XAML and I can set it but it doesn't actually do anything so I'm probably not using it right. Putting a dependency property in the view model doesn't sound right to me but I could be wrong.
I've had a look around the web - Microsoft documentation, blogs, articles, stack overflow, etc. - but I can't find anything that explains things well enough for me to figure out what I'm supposed to do. The writings I've found seem to be exclusively about getting information from a bound property - which I'm okay with - but what I'm after is setting a property from the XAML.
Can anyone give my any clues please? I don't know if I'm just a tiny step away from getting what I want or if I'm miles away.
This would set the Side property of the SelectorPage control to Source:
A view sets the property of a view model by two-way bind to it. For example, the following TextBox sets the string property of a view model called Test when you change the text in the TextBox:
<TextBox Text="{Binding Test, Mode=TwoWay}" />
So setting the property of a view model from the view typically applies to controls that handles some kind of input. Any default value of a source property should be defined in the view model:
private ConversionSide _side = ConversionSide.Source;
You shouldn't define the default values in the view.
I have created a UserControl with some DependencyProperties (in the example here only one string property). When I instantiate the Usercontrol, I can set the property of the UserControl and it is shown as expected. When I am trying to replace the static text by Binding, nothing is displayed.
My UserControl looks as follows:
<User Control x:Class="TestUserControBinding.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"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid>
<Label Content="{Binding MyText}"/>
</Grid>
</UserControl>
The Code Behind is:
namespace TestUserControBinding {
public partial class MyUserControl : UserControl {
public MyUserControl() {
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register(
"MyText",
typeof(string),
typeof(MyUserControl));
public string MyText {
get {
return (string)GetValue(MyTextProperty);
}
set {
SetValue(MyTextProperty, value);
}
}// MyText
}
}
When I try this in my MainWindow, everything is as expected:
<Window x:Class="TestUserControBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestUserControBinding"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyUserControl MyText="Hello World!"/>
</StackPanel>
</Window>
But this doesn't work:
<Window x:Class="TestUserControBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestUserControBinding"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyUserControl MyText="{Binding Path=Text}"/>
<Label Content="{Binding Path=Text}"/>
</StackPanel>
</Window>
The behaviour of the label is correct, so there is no Problem with the Property "Text"
What is my mistake?
With the following binding in your UserControl:
<Label Content="{Binding MyText}"/>
I'm not sure how setting the text directly to the MyText property works. You must be setting the DataContext on the UserControl somewhere for this to work.
Regardless, this binding is the issue - as I understand your scenario, you don't want to bind to the DataContext of the UserControl because that will not necessarily have a MyText property. You want to bind to the UserControl itself, and specifically the DependencyProperty you created. To do that, you need to use a RelativeSource binding, like the following:
<Label Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}, Path=MyText}"/>
This will navigate up the visual tree to MyUserControl and then find the MyText property there. It will not be dependent on the DataContext, which will change based on where you place the UserControl.
In this case, local refers to a namespace you'll need to define in the UserControl:
<UserControl x:Class="TestUserControBinding.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestUserControBinding"
...>
And your second example should work at that point.
There is a misunderstanding of how DataContexts are set. This is working against you...
Ultimately the binding to MyText on the user control, is not bound to the control's MyText dependency property but to the page's DataContext and there is no MyText property.
Let me explain
Explanation When the user control is put on your main page, it inherits its controls parent's DataContext (the StackPanel). If the parent's DataContext is not set, it will move up the chain to the StackPanel's parent's DataContext (ad Infinium) until it gets to the page's DataContext (which in your example is set and valid).
When you bind on the main page such as <local:MyUserControl MyText="{Binding Path=Text}"/> it looks for Text property on the main pages DataContext and sets the dependency property MyText to that value. Which is what you expect and it works!
Current State
So the state of the user control in your code is this, its DataContext is bound to the page's DataContext and MyText dependency property is set. But the internal control's binding to MyText fails. Why?
The user control has the parent's data context, and you are asking the control to bind to a MyText property on that data context. There is no such property and it fails.
Resolution
To bind to the control's instance and get the value from MyText property, just put a name (an element name) on the control such as
<User Control x:Class="TestUserControBinding.MyUserControl"
...
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="ucMyUserControl"
and then properly path the binding away from the default DataContext and to the elementnamed named instance called ucMyUserControl. Such as:
<Label Content="{Binding MyText, ElementName=ucMyUserControl }"/>
Note that VS2017/2019 will actually intellisense the ElementName after you have named the control.
Side Effect of Just Using The Parents Data Context
A side effect of the original situation without the resolution mentioned, is that you could just bind the user control's binding to Text and it will work because the binding is defaulting to the page's datacontext. Subtle...
<User Control x:Class="TestUserControBinding.MyUserControl"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid>
<Label Content="{Binding Text}"/>
That works and technically you could remove the dependency property. If the control is not used outside the project, it could be designed to bind to other named properties with no ill effect as well.
Then all usercontrols could become defacto sub controls of the main page, as if you just pasted the internal XAML onto the page.
I have the following xaml:
<Window x:Class="Retail_Utilities.Dialogs.AdjustPriceDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner" Name="Adjust_Price"
Title="Adjust Price" Background="#ee0e1c64" AllowsTransparency="True" WindowStyle="None" Height="330" Width="570" KeyDown="Window_KeyDown" Loaded="Window_Loaded">
<Grid Height="300" Width="550">
<ListBox HorizontalAlignment="Right" Margin="0,110,35,60" Name="lstReasons" Width="120" VerticalAlignment="Stretch"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=reasons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=POS_Price_Change_Reason}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Here is the relevant c#:
namespace Retail_Utilities.Dialogs
{
public partial class AdjustPriceDialog : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons; ...
and finally, here is the code from another page that opens this window:
AdjustPriceDialog apd = new AdjustPriceDialog();
apd.Owner = (Window)this.Parent;
apd.reasons = new ObservableCollection<Twr_POS_Price_Change_Reason>();
var pcr = from pc in ctx.Twr_POS_Price_Change_Reasons where pc.Deactivated_On == null select pc;
foreach (Twr_POS_Price_Change_Reason pc in pcr)
{
apd.reasons.Add(pc);
}
apd.AdjustingDetail = (Twr_POS_Invoice_Detail)lstDetails.SelectedItem;
if (apd.ShowDialog() == true)
{
}
When the dialog box opens, my lstReasons list is empty. I don't get any errors and when I place a stop in the code, I see that the reasons collection gets populated with the items from the table.
Reasons needs to be a Property (add { get; set;} ). Also, look at Visual Studio Output - it shows Binding errors, there should be some info about failed binding to reasons.
The problem seems to be How you are creating the property.
I know you put your prperty as an observable collection but this doesn't mean it is by it self observalble!
so you need to notify the UI when this property is changed by doing something in the setter like this:
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons
{
get{....}
set
{
Notify('reasons')
}
}
I don't remember the exact code because I didn't use WPF for a while but it is a method in INotifyPropertyChanged, good luck!
It seems your binding path is set to POS_Price_Change_Reason, while the name of your property is reasons. Unless you didn't include POS_Price_Change_Reason in your example code and reasons is the backing field for this property.
Also, keep in mind that you can only bind to public properties, not fields. Additionally, if you change the value of the property, you need to notify the view of this change, by invoking your PropertyChangedEventHandler event for that property:
PropertyChanged(new PropertyChangedEventArgs("YourPropertyName"));