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.
Related
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}"
Subj, how can i get it?
<controls:Pivot.TitleTemplate>
<DataTemplate>
<mainPivot:MyUserControl Name="MainPivotHeader"/>
</DataTemplate>
</controls:Pivot.TitleTemplate>
Tried to find it via VisualTreeFinders, but it sees only pivot item.
UserControl shows a picture, but it depends on user. During first initialization, it is empty, because user is not yet logged in. So, i'd like to force its update.
I can use mvvm light messaging, but i'm looking for self-sufficient components. This forcing is rare, so i dont want to use messaging here.
You should bind the Title property of the Pivot to a property on a ViewModel. Your DataTemplate would then have it's DataContext already set to that object. When you need to refresh, you call some method on that object.
Example
public class ViewModel : INotifyPropertyChanged
{
private MyTitleObject _titleObject;
public MyTitleObject TitleObject
{
get { return _titleObject; }
set
{
_titleObject = value;
OnPropertyChanged("TitleObject");
}
}
public void Refresh()
{
TitleObject = new MyTitleObject();
// or refresh values directly on the object
}
...
}
You xaml for your Pivot would need to following
<controls:Pivot Title="{Binding TitleObject}">
</controls:Pivot>
When you want to refresh, call the refresh on the viewmodel.
In a Windows Store split app, I want to pass a view model from a page to a user control. The scenario is that I want to reuse some common xaml in multiple pages, using a UserControl like a view.
In the main page:
<common:LayoutAwarePage
...
DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}"
... >
<views:MyUserControlView Model="{Binding ViewModel}" />
...
In the user control code:
public sealed partial class MyUserControlView : UserControl
{
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(MenuSource),
typeof(MyUserControlView), null);
...
public ModelType Model
{
get
{
return this.GetValue(ModelProperty) as ModelType ;
}
set
{
this.SetValue(ModelProperty, value);
}
}
The Model setter is never called. How do I hook up the user control to the parent page's view model?
Or, is there a better way to implement shared views for use in pages?
Thanks.
-John
Correct binding would be:
<views:MyUserControlView Model="{Binding}" />
You've already set DataContext for the page above. All bindings are relative to the current DataContext.
The setter still won't be called, though. It is just a wrapper to access the DependencyProperty from code. Binding will call SetValue directly.
Depending on your requirements you might not even need to define your own Model DependencyProperty. Each control automatically inherits DataContext from its parent control. In your example above the user control already has its DataContext set to the same view model as the page.
I'm rather new to XAML and Silverlight. I have a XAML page and a code behind class for it. In the class, I have a protected read-only property. Can I bind a control to that property? Trying to specify the root element of the XAML as the DataContext (by name, as an ElementName) causes a designer error "Value does not fall within the expected range."
EDIT: I'd like to do in the designer-fiendly way. I understand I can do everything (including control population) from code; that's not the point. Can I have the designer recognize and display the properties of my code-behind class? Not one ones from the base (PhoneApplicationPage) but the ones that I define?
Your code behind should be the datacontext.
For example on a main page code behind:
public MainPage()
{
InitializeComponent();
DataContext = this;
}
You should be able to bind to the protected property but only one way ie from the property to the xaml. As it is read-only you will not be able to get the value if it is changed on the page by the user.
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.