Binding entire UserControl to a property - c#

My viewModel gets a UserControl from a service. I want to display that UserControl.
XAML looks like:
<Grid >
<ContentPresenter Content="{Binding AddInUI}">
</ContentPresenter>
</Grid>
And the ViewModel's property like:
public UIElement MyUI
{
get
{
return myUI;
}
set
{
Set(ref myUI, value);
}
}
So I fetch the ui and set to this VM's property. However, nothing renders in the UI. The getter of the MyUI property never gets called, even after I assign value to it, like:
MyUI = 'some user control';

A VM would not contain a UI-specific element like this. It would however retrieve a ViewModel context for MyUI property and use a DataTemplate in the XAML to render that ViewModel Type appropriately.

Implement INotifyPropertyChanged and notify MyUI property in set of MyUI property.

Related

Create a UI element placeholder in a window and load a UI element in that placeholder during runtime

In a WPF window, I want to create a placeholder and during the runtime I would like to render or load a UI element in it.
A placeholder in a window looks something like,
<Grid>
<Placeholder1/>
</Grid>
during the runtime, I would like an IconImage to be rendered there.
<Placeholder1>
<Image Source="pack//...." Loaded="Icon_Loaded" /> // Static source
</Placeholder1>
I wish to render the placeholder with the Image during runtime everytime I change Image's property, which should eventually trigger the Icon_Loaded event. I dont want to render the Image(Placeholder) on the startup, but later on.
Background story as to why I wish for a placeholder solution is,
in the below part of XAML part, I wish to trigger the Loaded event handler everytime I change the IconPack's binding property. So as a solution I reckoned, if I render the IconPack element in a placeholder during the runtime everytime I change its binding property, I get to trigger the Loaded event handler for every binding property change.
<IconPack:PackIconFontAwesome Grid.Column="2" Kind="{Binding cbCheckButtonCaptionIconType}"
Foreground="{Binding cbCheckButtonCaptionIconColor}"
Visibility="{Binding cbCheckButtonCaptionIsVisible}"
Loaded="PackIconFontAwesome_Loaded"/>
Could you please show me how to use ContentControl to do the same.
(I tried using TargetUpdated, but it doesnt help in my scenario, so the only option I have is Loaded)
It really depends on what you are trying to do. If your are always using images, use the Image control and bind the source to a property on your view model that contains the path.
private string _imagePath;
public string ImagePath
{
get => _imagePath;
set
{
if (_imagePath == value)
return;
_imagePath = value;
OnPropertyChanged(nameof(ImagePath));
}
}
<Grid>
<Image Source="{Binding ImagePath, TargetNullValue={x:Null}}" />
</Grid>
For more complex content, usually you would use a ContentControl. It has a Content property that you can assign any controls or bind to a property on a view model. It can be used with data templates (if you are not using data templates, you could also use other controls as placeholder, e.g. a Border.).
If you are directly assigning a control to the placeholder in code-behind, it could look like this.
<Grid>
<ContentControl x:Name="Placeholder" />
</Grid>
Placeholder.Content = new Image(...) {...};
If you want to bind a property on a view model and leverage data templating, you could create types:
public class ImageItem
{
public ImageItem(string path)
{
Path = path;
}
public string Path { get; }
}
ImagePath = ...;
Expose a property for the content item in your view model and assign it, e.g.:
public object ContentItem
{
get => _contentItem;
set
{
if (_contentItem == value)
return;
_contentItem = value;
OnPropertyChanged();
}
}
ContentItem = new ImageItem(...);
Then you could create a DataTemplate for this concrete type in XAML.
<DataTemplate DataType="{x:Type local:ImageItem}">
<Image Source="{Binding Path, TargetNullValue={x:Null}}"/>
</DataTemplate>
The resources must be in scope of the ContentControl, then it will automatically select the appropriate data template for the type bound to the Content property.
<Grid>
<ContentControl Content="{Binding ContentItem}" />
</Grid>
As you want to change the content at runtime, please be aware that it is essential to implement INotifyPropertyChanged when binding to properties, otherwise changes to properties will not be reflected in the user interface.

Why is the binding on my custom control not working?

I have a MainView where I'm using a component I created called VideoPlayer. VideoPlayer is a Grid that has a MediaElement as well as some buttons and a slider to control video playback.
I'm trying to set the path of the video to be played in MainView by binding a property in VideoPlayer to a property in MainView.
The relevant parts of the code are below:
Snippet from VideoPlayer.xaml:
<MediaElement
Name="MediaElement"
Grid.Row="0"
LoadedBehavior="Manual"
Stretch="Uniform"
Source="{Binding VideoLocation}" />
Snippet from VideoPlayer.xaml.cs:
public string VideoLocation
{
get { return (string)GetValue(VideoLocationProperty); }
set { SetValue(VideoLocationProperty, value); }
}
public static readonly DependencyProperty VideoLocationProperty
= DependencyProperty.Register("VideoLocation", typeof(string), typeof(VideoPlayer), new PropertyMetadata(null));
VideoPlayer is used in MainView.xaml, which is following MVVM:
<ReuseableComponents:VideoPlayer VideoLocation="{Binding VideoPath}"/>
This is the bound property, VideoPath, in MainViewModel.cs:
private string _videoPath;
public string VideoPath
{
get => _videoPath;
set => SetProperty(ref _videoPath, value);
}
If I remove the VideoLocation binding and hard code a path in MainView.xaml, the video will play just fine:
<ReuseableComponents:VideoPlayer VideoLocation="C:\Movies\FightClub.mp4"/>
So, I think the issue is with the MainView binding and not the VideoPlayer binding.
All of my other property bindings in MainView work, and they all follow this pattern:
<ComponentName PropertyName="{Binding ViewModelPropertyName}">
where ViewModelPropertyName is defined in MainView with a backing field and the setter calls SetProperty()
EDIT
The error I made was: in the constructor for VideoPlayer I had a line DataContext = this; I think I saw it in some tutorial somewhere. Anyway as per #Magnus advice I removed the line and changed VideoPlayer.xaml to
<UserControl x:Name="TheControl" ...>
<Grid DataContext={Binding ElementName=TheControl} ...>
...
</Grid>
</UserControl>
Where previously the UserControl didn't have the name property set and the Grid didn't have the DataContext property set.
The problem might be that you are setting the DataContext of your VideoPlayer UserControl to itself on the top level. That means you override the MainView DataContext that the control should inherit, i.e., this binding:
<ReuseableComponents:VideoPlayer VideoLocation="{Binding VideoPath}"/>
tries to bind to the VideoPlayer control instead of the MainView DataContext.
You could instead set the internal DataContext of your VideoPlayer on a contained element, something like this:
<UserControl x:Name="TheControl" ...>
<Grid DataContext={Binding ElementName=TheControl} ...>
...
</Grid>
</UserControl>
The Binding is being done relative to your control's DataContext, which you probably haven't set. You need to set it relative to the control instead. Give your control a name like this:
<UserControl x:Class="YourApp.YourUserControl"
... etc ...
x:Name="_this">
And then bind to that directly:
Source="{Binding ElementName=_this, Path=VideoLocation}"
Alternatively you can also use a RelativeSource binding with AncestorType set to your user control type.

How can I bind a property?

I'm using the Bing map SDK in my WPF application and the XAML looks like:
<m:Map
x:Name="MyMap"
Grid.Row="1"
CredentialsProvider="KEY"
ZoomLevel="{BINDING MapZoomLevel}"
Mode="Road">
The code behind:
private int mapZoomLevel;
public int MapZoomLevel { get { return mapZoomLevel; } set { mapZoomLevel = value; NotifyOfPropertyChange(() => MapZoomLevel); } }
But this aint working. I guessing it is because I've already bound the Map by setting x:Name. The problem is that I can't remove the x:Name since I'm doing some setup in the view but is there a workaround? I would like to be able to bind the ZoomLevel of the map somehow
In order to data bind, you need to do a few things:
1) You must set the DataContext of the UserControl or Window to the object that contains the property that you want to bind to. That could be like this (in the UserControl or Window code behind) if that object is a separate view model class:
DataContext = new SomeTypeOfViewModel();
Or like this if the property is declared in the code behind:
DataContext = this;
2) You must implement the INotifyPropertyChanged interface or implement DependencyPropertys - you seem to have implemented the INotifyPropertyChanged interface, but you must ensure that you have done it correctly.
3) You must provide a valid Binding Path... BINDING is not valid, so an appropriate Binding Path for you might be this (depending on where you have declared your property):
<m:Map x:Name="MyMap" Grid.Row="1" CredentialsProvider="KEY"
ZoomLevel="{Binding MapZoomLevel}" Mode="Road">
Please read the Data Binding Overview page on MSDN for the full story.
based on your tags you are using Caliburn Micro with this? Datacontext is already set with viewmodel/view from the framework. ZoomLevel="{Binding MapZoomLevel, Mode=TwoWay}" is required.

How do you bind to a property in a xaml code-behind if the usercontrol's datacontext is a view-model?

I have a UserControl who's DataContext is being set to an instance of a ViewModel (using MVVM). But, I have controls within the UserControl which need to be bound to properties that only pertain to the view (which is why I placed them in code behind). I'm not sure how to bind this in xaml appropriately:
Note: SelectedOrderType is a property on the View-Model, and OrderTypes is a property on the UserControl itself.
<UserControl x:Class="MyNamespace.OrderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="OrderUserControl">
<Grid>
...
<ComboBox ItemsSource="{Binding Path=OrderTypes, ElementName=OrderUserControl}"
SelectedValue="{Binding Path=SelectedOrderType}"
SelectedValuePath="OrderTypeCode"
DisplayMemberPath="OrderTypeName" />
</Grid>
</UserControl>
public partial class OrderControl : UserControl
{
public OrderControl()
{
InitializeComponent();
OrderTypes = ...;
}
public IReadOnlyCollection<OrderTypeInfo> OrderTypes { get; private set; }
}
Also, I know I can simply create a property on the View-Model, and I get that some people would suggest that that would be the correct place to put it... but I really would like to know how I could do what I'm attempting to do if not for this scenario, maybe for other scenarios in the future?
I may be wrong but would you not need to make a dependency property on your user control for "SelectedOrderType" and bind the the View Model to that property not bind directly to the view model from the user control.
That way your UserControl is not dependent on the view model?
Edit:
I think you could set it up the way you have it, but the binding for SelectedOrderType would need to be something like {Binding Path=DataContext.SelectedOrderType, ElementName=OrderUserControl}

Setting ViewModel's Property from XAML

I have some UserControl, It's DataContext is binded to the ViewModel,
How to set a ViewModel's property from XAML? Is it possible?
UPD :
Sorry for being not very clear,
I'm trying to get something like this :
UserControl's DataContext is binded to ViewModel, I need to set ViewModel's property to something (let's say, UserControl's Width property).
Is it possible?
UPD2: It seems to be not possible.I know about TwoWay binding mode, etc, thing I wanted to do - to set ViewModel's property to UserControl's one
This example should be very clear
<Set Property={Binding SomePropertyOnViewModel}
Value={Binding RelativeSource={RelativeSource Self},
Path=SomePropertyOnUserControl}>
I am not sure whether I understand the question exactly.
But here is an example.
It will:
Create a view model of type ExampleViewModel inside the user control by setting the user
controls DataContext property in xaml
Create a text box in xaml and bind it to the view models
TextInViewModel string property.
Set up the usual INotifyPropertyChanged interface (this was extracted to the base class ViewModelBase)
Create the view model in xaml and set the user controls data context to it:
<UserControl x:Class="MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<UserControl.DataContext>
<viewModel:ExampleViewModel/>
</UserControl.DataContext>
<StackPanel Orientation="Horizontal" >
<Label>Enter Text here: </Label>
<TextBox Text="{Binding TextInViewModel}"></TextBox>
</StackPanel>
</UserControl>
ViewModel:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
public class ExampleViewModel : ViewModelBase
{
/// <summary>
/// Property bound to textbox in xaml.
/// </summary>
public String TextInViewModel
{
get { return _textInViewModel; }
set
{
_textInViewModel= value;
RaisePropertyChanged("TextInViewModel");
}
}
private string _textInViewModel;
/// <summary>
/// Constructor.
/// </summary>
public ExampleViewModel()
{
}
}
Binding works both ways: i.e. from source (e.g. viewmodel) to target (e.g. usercontrol) and from target back to source.
You specify the direction via Mode of binding.
Following are the BindingModes:
TwoWay
OneWay
OneTime
OneWayToSource
In your case, if you want to bind width property of usercontrol to the TheWidth property of ViewModel:
Case A:
Want to bind in both directions, use Mode=TwoWay
<UserControl Width="{Binding TheWidth, Mode=TwoWay}">
<!-- your rest of code -->
</UserControl>
Case B:
Want to bind only from usercontrol to viewmodel, use Mode=OneWayToSource
<UserControl Width="{Binding TheWidth, Mode=OneWayToSource}">
<!-- your rest of code -->
</UserControl>
XAML
<UserControl.DataContext>
<vm:ViewModel/>
</UserControl.DataContext>
I prefer the ViewModel Locator approach (this is like a service locator pattern for ViewModel).
Because as soon as your ViewModel has constructor parameters, you are either tightly coupled, or you can't use the above described xaml way....
There are many ViewModel-Locator ways. One is described here using MEF and silverlight.
http://johnpapa.net/simple-viewmodel-locator-for-mvvm-the-patients-have-left-the-asylum
here is another one:
http://brendan.enrick.com/post/Wire-up-your-ViewModels-using-a-Service-Locator.aspx
Well, you bind your UI elements to them:
<UserControl Width="{Binding Path=DisplayWidth, Mode=OneWayToSource}">
<Grid>
<TextBox MinWidth=100 Text="{Binding MyProperty}"/>
</Grid>
</UserControl>
assuming a view model like this:
class ViewModel
{
public string MyProperty { get; set; }
public int DisplayWidth { get; set; }
}
Through binding my dear friend..
for example: (Assuming in your context)
If you have class "Person" and your person has a Name and SurName public property and you want to bind it to a textbox. You do the following:
<TextBox Text="{Binding Path=Name}" />
This only works if the name is your public property, it is best practice to make you object ( in this case Person) as a public property and use the Path parameter differently.
Example:
<TextBox Text="{Binding Path=Person.Name}" />
It does clutter your code way less, then to make a property in your viewmodel for every property of any object in your viewmodel.
"How to set a ViewModel's property from XAML? Is it possible?"
So, that seems to be not possible, max you can accomplish - two-way binding, which is, unfortunately not I wanted.
All in all it's rather bad design than a problem

Categories