why does x:Bind expect a static method to bind to? - xaml

I have a UWP MVVM application where I bind, amongst others, the following property to a DataGridComboBoxColumn:
public List<ComboBoxValues> ListValues { get; set; } = new List<ComboBoxValues>();
XAML:
xmlns:local="using:MyProject.ViewModels"
<controls:DataGridComboBoxColumn Header="myHeader"
Binding="{Binding theSelectedValue, Mode=TwoWay}"
ItemsSource="{x:Bind local:PageVM.ListValues, Mode=OneWay}"
DisplayMemberPath="theValueOptions"/>
I use dependency injection, using Autofac to generate an instance of my viewModels when needed:
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<PageVM>().AsSelf();
I get the error: Static method 'ListValues' not found in type 'PageVM'. I have tried googling it, and the only results that I find is that it should not be that easy to bind to a static property etc.
Why is x:bind expecting a static method? I don't want to use static properties/methods.
EDIT:
The DataContext of pages are also set using a NavigationService and ViewModelBinder with code, though NOT the code-behind page. Autofac, the IoC manages the instances of those, the NavigationService and the ViewModelBinder. Thus, I do not know how to link the XAML to those instances to use x:Bind?
I do not want to use code behind, as I am trying to strictly stick to MVVM.

Yeah so the problem is the below line:
ItemsSource="{x:Bind local:PageVM.ListValues, Mode=OneWay}"
What you're doing with this code is, telling the xaml BindingEngine to look for a class called PageVM under the xmlns:local and then look for a field called ListValues. Now since we don't have an instance of the PageVM (as per the above line), it's considering ListValues as static and is trying to find it.
Generally to bind to a ViewModel, you set the DataContext of the Page or UserControl. You can do so like:
<Page.DataContext>
<local:PageVM x:Name="ViewModel"/>
</Page.DataContext>
if you're using a UserControl the above would look like:
<UserControl.DataContext>
<local:PageVM x:Name="ViewModel"/>
</UserControl.DataContext>
and now use it in xaml code like below:
<controls:DataGridComboBoxColumn Header="myHeader"
Binding="{Binding theSelectedValue, Mode=TwoWay}"
ItemsSource="{x:Bind ViewModel.ListValues, Mode=OneWay}"
DisplayMemberPath="theValueOptions"/>
Notice the x:Name="ViewModel", as part of the DataContext. This would allow you to reference your ViewModel via the codebehind too.
Edit After comments
Alternatively, if you cannot have a public parameterless constructor then I suggest you use the codebehind to create an instance of the ViewModel like below:
public PageVM ViewModel => this.DataContext as PageVM;
There is no clause that performs a null check on the above property since from the comments, the data context is being set via a dependency injection framework
and then use it the same way in your xaml code:
ItemsSource="{x:Bind ViewModel.ListValues, Mode=OneWay}"

Related

Property can't be found on ViewModel in UWP app

An Order form in UWP using Template 10 adds products to an order. The error is
Invalid binding path 'OrderViewModel.FindProduct_TextChanged' : Property 'OrderViewModel' can't be found on type 'ProductViewModel'
The relevant xaml snippet is
<Page.DataContext>
<ViewModels:MainPageViewModel x:Name="OrderViewModel" />
</Page.DataContext>
<GridView ItemsSource="{x:Bind OrderViewModel.Products, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="ViewModels:ProductViewModel" >
<AutoSuggestBox
Name="ProductAutoSuggestBox"
TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
TextChanged="{x:Bind OrderViewModel.FindProduct_TextChanged}">
</AutoSuggestBox>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The relevant snippet from the OrderViewModel and the ProductViewModel
namespace ViewModels
{
public class OrderViewModel : ViewModelBase
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();
public void FindProduct_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{ ... }
}
public class ProductViewModel : ViewModelBase
{
string _ItemCode = default(string);
public string ItemCode { get { return _ItemCode; } set { Set(ref _ItemCode, value); } }
public ProductViewModel()
{
}
}
}
How to I correctly reference FindProduct_TextChanged on the OrderViewModel from the DataTemplate for the GridView which references ProductViewModel?
Voted up to #tao's comment. #Vague, I think you may misunderstand what x:DataType is used for. You can refer to the "DataTemplate and x:DataType" part of Data binding in depth:
When using {x:Bind} in a data template, so that its bindings can be validated (and efficient code generated for them) at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType.
For your scenario, from your code public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();, the type of your DataTemplate's data object should be your Product class, not your ProductViewModel, and in the meanwhile, your FindProduct_TextChanged event must be find in this Product class, that means your code of FindProduct_TextChanged should be placed in your Product data model.
By the way, I think there is no need to use TwoWay binding for ItemsSource. For this scenario, the binding target is ItemsSource of GridView, the binding source is ObservableCollection<Product> Products, I understand you want to update GridView when your collection is updated, this is work is done with ObservableCollection. Besides, only the binding source here can be changed to notify the binding target, so OneWay binding is enough. But it's not a big problem with your code.
So for your GridView, it should be something like this:
<GridView ItemsSource="{x:Bind OrderViewModel.Products, Mode=OneWay}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="Models:Product" >
<AutoSuggestBox
Name="ProductAutoSuggestBox"
TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
TextChanged="{x:Bind FindProduct_TextChanged}">
</AutoSuggestBox>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
if error is kind of like this I approved its a charset support bug:
Error Invalid binding path 'XX.YY' : Property 'ZZ' can't be found on type 'CCC'
Either xaml and C# supports unicode;
its because you use a non-ascii character in class properties. this is a bug I found today. Just rename your class proprty characters to ascii standards. Hope it will be fixed.

Prism MVVM Binding to User Control Dependency Property

I have a Universal app that I'm writing and I want to use Prism and Unity as my MVVM framework. Everything was going great until I got to a view where I have multiple instances of the same user control(a custom Watermark Textbox). For some reason, I haven't been able to find a good solution to my problem. I imagine I'm overlooking something and there is a straightforward answer.
Here's my source code(just the relevant portions). Some background, I had this working before implementing Prism. The Commands attached to the user controls are firing as expected but I can't figure out how to manipulate the control itself):
Any guidance on how to use user controls with Prism or binding to dependency properties with Prism would be great. Thanks.
My View
<!-- I want to be able to set the watermark and also retrieve the text from my ViewModel -->
<uc:WatermarkTextBox Width="250"
x:Name="FullName">
<i:Interaction.Behaviors>
<iCore:EventTriggerBehavior EventName="GotFocus">
<iCore:InvokeCommandAction Command="{Binding EntryFieldFocus}"
CommandParameter="{Binding ElementName=FullName}"/>
</iCore:EventTriggerBehavior>
</i:Interaction.Behaviors>
</uc:WatermarkTextBox>
<!-- This one as well -->
<uc:WatermarkTextBox Width="250"
x:Name="EmailAddress">
<i:Interaction.Behaviors>
<iCore:EventTriggerBehavior EventName="GotFocus">
<iCore:InvokeCommandAction Command="{Binding EntryFieldFocus}"
CommandParameter="{Binding ElementName=EmailAddress}" />
</iCore:EventTriggerBehavior>
</i:Interaction.Behaviors>
</uc:WatermarkTextBox>
Going from your comments/error message ("Failed to assign to property %0") when attempting to bind the property directly, the Property you are trying to bind to isn't a dependency property.
Your WatermarkTextBox only implements a CLR Property, like this:
public class WatermarkTextBox : TextBox {
public object Watermark { get; set; }
}
This can't be used for databinding.
You need to implement it as Dependency Property, like
public class WatermarkTextBox : TextBox {
public object Watermark
{
get { return (object)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
public static readonly DependencyProperty WatermarkProperty =
DependencyProperty.Register("Watermark", typeof(object), typeof(WatermarkTextbox), new PropertyMetadata(null));
}
This will allow to use <uc:WatermarkTextBox Watermark={Binding watermarkText, Mode=TwoWay} for example.
If you can't modify WatermarkTextBox (third-party closed source control), then you'll have to implement an attached behavior and then bind your ViewModel Property to the attached behavior and assign the Watermark property from within it.
If you need TwoWay binding (which is unlikely in case of Watermark property, since it won't be changed from UI), you have to register an event handler (KeyDown in linked answer) to this specific event and pass the new value to the attached property
Except these two ways, there is no way to update a Control's CLR property without violating MVVM.

TwoWay Binding of a ComboBox to a static property in .NET 4.5

Well, i just posted this question, but i figured that i am not doing anything wrong. My code (and the code of all of the answers) is correct, but my dev-maschine runs with .NET4.5 which apparently has a problem with the binding of the combobox...
So here a new version of the question: How to two-way bind a combobox's SelectedItem to a static Property in .NET4.5?
The following code snippets work in .net4 but not in .NET4.5. In 4.5 it is just that the selected value is not propagated back into my static property.
My ViewModel:
public class MainWindowViewModel
{
public static List<String> MyElements { get; set; }
public static string SelectedElement { get; set; }
static MainWindowViewModel()
{
MyElements = new List<string>() {"a", "b", "c"};
SelectedElement = "a";
}
}
And my XAML
<Window.Resources>
<me:MainWindowViewModel x:Key="model"/>
</Window.Resources>
<StackPanel>
<ComboBox
ItemsSource="{Binding Source={x:Static me:MainWindowViewModel.MyElements}, Mode=OneWay}"
SelectedItem="{Binding Source={StaticResource model}, Path=SelectedElement}" />
</StackPanel>
Does anybody have an idea how to achieve this two-way binding of a ComboBox's SelectedItem to a static Property in .NET4.5?
Please refer to my report on Microsoft Connect. Every Selector control is infected by this issue.
Finally, a few weeks ago Microsoft released an appropriate patch which has already been distributed through Windows Update. see Knowledge Base KB2805222 (WPF - Issue 7)
WPF - Issue 7:
Assume that you change a selector property (such as, the SelectedItem property) or ComboBox.Text property by using a binding path that contains a static property. In this situation, the binding does not react to the changes. Specifically, the new value is not written to the data item.

Is there something similar to TabControlRegionAdapter.ItemContainerStyle Attached Property for ItemsControl?

I'm using Prism 4 with Silverlight and I want to use ItemsControl to host multiple views. I really want all the views to be wrapped inside a specified ItemTemplate or be able to specify an ItemStyle so that I can use something like the Expander control in the Silverlight Toolkit . When I try to specify an ItemTemplate an unhandled System.NotSupportedException is thrown at runtime.
ItemsControl.Items must not be a UIElement type when an ItemTemplate is set.
at System.Windows.Controls.ItemsControl.MS.Internal.Controls.IGeneratorHost.GetContainerForItem(Object item, DependencyObject recycledContainer)
at System.Windows.Controls.ItemContainerGenerator.Generator.GenerateNext(Boolean stopAtRealized, Boolean& isNewlyRealized)
at System.Windows.Controls.ItemContainerGenerator.System.Windows.Controls.Primitives.IItemContainerGenerator.GenerateNext(Boolean& isNewlyRealized)
at System.Windows.Controls.ItemsControl.AddContainers()
at System.Windows.Controls.ItemsControl.RecreateVisualChildren(IntPtr unmanagedObj)
Code
<ItemsControl Regions:RegionManager.RegionName="DetailsRegion">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter Content="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It has been a while since I used PRISM, but the following is an example that you can use to implement a custom IRegion that wraps the element before adding it to the collection.
public class RegionWrapper : Region
{
public override Microsoft.Practices.Composite.Regions.IRegionManager Add(object view, string viewName, bool createRegionManagerScope)
{
var myWrapper = new Wrapper();
myWrapper.Content = view;
return base.Add(myWrapper, viewName, createRegionManagerScope);
}
}
To register this item you need to create a Region factory, which in PRISM they call an adapter
public class RegionWrapperAdapter : RegionAdapterBase<IRegionAdapter>
{
protected override Microsoft.Practices.Composite.Regions.IRegion CreateRegion()
{
return new RegionWrapper();
}
}
Then on your Bootstrap just register your adapter
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var regionAdapterMappings = base.ConfigureRegionAdapterMappings();
regionAdapterMappings.RegisterMapping(typeof(ItemsControl), Container.Resolve<RegionWrapperAdapter>());
return regionAdapterMappings;
}
Of course the other part left is to implement the control 'Wrapper' so you can create that class and add the content. It could simply be a ContentControl with a particular style similar to what you have in this example or add anything fancier.
This code is based on an old version of PRISM, so things might have changed recently.
Hope this helps
Miguel

Getting Unity to Resolve views in XAML

I'm starting out with MVVM, and I'm starting to understand things. I'm currently experimenting with the Cinch framework, though I'm not committed to it as of yet.
I was injecting the ViewModels into the Views using by having a reference to the ViewModel in the codebehind of the view, with the property having a [Dependency] on it, and in the setter it sets the DataContext to the right view, using Unity. Neat trick, I thought.
I'm trying to get my app to work as a single Window, with injected views (As opposed to multiple windows and dealing with opening\closing them)
I changed my views from Windows to UserControls, and added a to the main window.
That worked, but the ViewModel was never injected, presumably because the XAML doesn't use Container.Resolve to create the view, as when I created the view and added it manually in the code-behind using Resolve, the [Dependency] was created.
How can I set up my window, so that if I add a view through XAML, or the view gets changed as a result of a UI action etc, it gets it through Unity, so that it can work its magic?
This problem is normally solved using Regions and the RegionManager. In the main window ViewModel, a set of Regions is created and added to the RegionManager. Then ViewModels can be Resolved and added to the Region.Views collection.
In XAML, the Region is normally injected by having the ItemsSource property of an ItemsControl bound to the region property of the main ViewModel.
So, in the main screen ViewModel you would have something like this:
public class TestScreenViewModel
{
public const string MainRegionKey = "TestScreenViewModel.MainRegion";
public TestScreenViewModel(IUnityContainer container, IRegionManager regionManager)
{
this.MainRegion = new Region();
regionManager.Regions.Add(MainRegionKey, this.MainRegion);
}
public Region MainRegion { get; set; }
}
This would be Resolved normally in your IModule
#region IModule Members
public void Initialize()
{
RegisterViewsAndServices();
var vm = Container.Resolve<SelectorViewModel>();
var mainScreen = Container.Resolve<TestScreenViewModel>();
mainScreen.MainRegion.Add(vm);
var mainView = ContentManager.AddContentView("Test harness", mainScreen);
}
#endregion
And the XAML representation of your template looking something like
<DataTemplate DataType="{x:Type TestModule:TestScreenViewModel}">
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Auto">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=MainRegion.Views}" />
</StackPanel>
</ScrollViewer>
</DataTemplate>
The way to solve your problem is to make your window to have a ViewModel as well, with ViewModels of UserControls exposes as properties on it. Then in your XAML for a window you'd simply use Binding mechanism to bind UserControl's DataContexts to proper properties of your your main ViewModel. And since that main ViewModel is resolved from Unity container it would have all other ViewModel-s injected as needed.