wpf usercontrol data-binding using xaml - c#

I have wfp form like that:
public partial class MediaPlayerControlMain : Window
{
MediaPlayerMain MediaPlayerMain;
public MediaPlayerControlMain()
{
MediaPlayerMain = new MediaPlayerMain();
InitializeComponent();
}
}
I have my user control (PlayList) that use MediaPlayerMain object.
That User Control have that:
public partial class PlayList : UserControl
{
public MediaPlayerMain MediaPlayer
{
get { return (MediaPlayerMain)GetValue(MediaPlayerProperty); }
set { SetValue(MediaPlayerProperty, value); }
}
public static readonly DependencyProperty MediaPlayerProperty =
DependencyProperty.Register(
"MediaPlayer", typeof(MediaPlayerMain), typeof(PlayList),
new FrameworkPropertyMetadata()
);
}
Is there the way to set MediaPlayer property using just xaml. I tried to use "{Binding ElementName=MediaPlayerMain}" but it seems to be that MediaPlayerMain haven't initialized yet. Although i initialized it before InitializeComponent() function. What am i doing wrong?. And what is the best option to pass this object to my user control?

public partial class MediaPlayerControlMain : Window,INotifyPropertyChanged
{
public MediaPlayerControlMain()
{
InitializeComponent();
MediaPlayerMain = new MediaPlayerMain();
}
private MediaPlayerMain mediaPlayerMain;
public MediaPlayerMain MediaPlayerMain
{
get { return mediaPlayerMain; }
set { mediaPlayerMain = value; Notify("MediaPlayerMain"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
"{Binding MediaPlayerMain RelativeSource={RelativeSource AncestorType=Window}}"
The issue is you are trying to bind the field not property.For binding source must be the property not field because binding system uses reflection and looks only for properties not for fields.I hope this will help.

You need to name your root element (the Window/UserControl itself) in the markup. Ie:
<Window x:Name="mediaPlayer"
....>
<TextBlock Text="{Binding SomeProperty,ElementName=mediaPlayer}"

I don't see you setting the DataContext anywhere, and I don't think you have an object named MediaPlayerMain in your XAML tree
When you write {Binding ElementName=MediaPlayerMain}, you are telling WPF to set the property equal to the XAML object in the Visual Tree that is named MediaPlayerMain
What you're probably looking for instead is to bind to a Property in the DataContext named MediaPlayerMain, in which case your binding would look like this:
<local:PlayList MediaPlayer="{Binding MediaPlayerMain}" />
But that won't work unless your DataContext is set to an object containing the property MediaPlayerMain, such as setting this.DataContext = this in your Window's constructor.
As an alternative, you can use ElementName in your binding to tell WPF to look up the property on an object in the Visual Tree instead of in the DataContext, such as your Window.
<local:MediaPlayerControlMain x:Name="MainWindow" ...>
<local:PlayList MediaPlayer="{Binding MediaPlayerMain, ElementName=MainWindow}" />
</local:MediaPlayerControlMain>
This will make your binding look for the property MediaPlayerMain in the XAML element named MainWindow, which is your MediaPlayerControlMain class.
If you're new to WPF's DataBinding, I actually have an article on my blog specifically for beginners that should help you understand better what it is and how it works: What is this "DataContext" you speak of?

Related

UWP bugs? Binding to some nested DependencyProperty would not working

I'm developing a chart control which is derived from a UserControl, everything is fine until I found that a DependencyProperty of bottom axis can not be set through binding. Here is the code snippet of the chart control:
public class AxisBase : FrameworkElement
{
public IList<double> ExtraGridLines
{
get { return (IList<double>)GetValue(ExtraGridLinesProperty); }
set { SetValue(ExtraGridLinesProperty, value); }
}
public static readonly DependencyProperty ExtraGridLinesProperty =
DependencyProperty.Register("ExtraGridLines", typeof(IList<double>), typeof(AxisBase), new PropertyMetadata(null, OnExtraGridLineDataPropertyChanged));
private static void OnExtraGridLineDataPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Problem: data bing would not work, and
//this call back method will never get called!
Debug.WriteLine("ExtraGridLines changed...");
}
}
public sealed partial class MyChart : UserControl
{
public MyChart()
{
this.InitializeComponent();
}
public AxisBase BottomAxis
{
get { return (AxisBase)GetValue(BottomAxisProperty); }
set { SetValue(BottomAxisProperty, value); }
}
public static readonly DependencyProperty BottomAxisProperty =
DependencyProperty.Register("BottomAxis", typeof(AxisBase), typeof(MyChart), new PropertyMetadata(null));
}
and here is the binding code:
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
x:Name="rootPage">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:MyChart>
<local:MyChart.BottomAxis>
<local:AxisBase ExtraGridLines="{Binding MyExtraGridLines, ElementName=rootPage}" />
</local:MyChart.BottomAxis>
</local:MyChart>
</Grid>
</Page>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private List<double> _myExtraGridLines;
public List<double> MyExtraGridLines
{
get { return _myExtraGridLines; }
set
{
_myExtraGridLines = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyExtraGridLines"));
}
}
public MainPage()
{
this.InitializeComponent();
this.MyExtraGridLines = new List<double>() { 10, 100, 1000 };
}
public event PropertyChangedEventHandler PropertyChanged;
}
The problem is: the binding seems never get worked, the PropertyChangedCallback method of dp ExtraGridLines is never called. However these code works in WPF. What's wrong with my code? or is it a bug?
Any idea would be appreciate!
Edit1:
This seems having nothing to do with data type, even I've changed the type from IList<double> to int, string or object, the problem is same. But if I change the type of AxisBase from FrameworkElement to DependencyObject, then the binding would work. But this solution is not acceptable, since DependencyObject has no Style.
Edit2:
Finally, I think I've found the reason why ElementName binding would not work: ElementName binding only works for elements in the same NameScope. If you're interested, for more information, please see these two good links:
https://stackoverflow.com/questions/18389118/how-does-binding-elementname-work-exactly and
http://www.cnblogs.com/idior/archive/2010/05/28/1746513.html
btw 1: I was wrong at the very first: these code would not work in WPF either. The binding would throw a runtime error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=rootPage'. BindingExpression:Path=ShowAxisLabel; DataItem=null; target element is 'AxisBase' (Name=''); target property is 'IsLabelVisible' (type 'Boolean')
btw 2: To make DataContext binding work in the above code, AxisBase should listen to the change of the DataContext of MyChart, or be added to the Logical or Visual tree of MyChart explicitly. That's a bug of my code. But due to NameScope problem, there seems no normal or elegant way to make ElementName binding here.
Probably I should change the title of this question to: Why ElementName binding to element in UserControl would not work in UWP/WPF?
This is likely a bug with traditional binding in UWP; however, with the new x:Bind, the following code should be working as expected.
<local:AxisBase ExtraGridLines="{x:Bind MyExtraGridLines, Mode=OneWay}" />
Note this gives you better performance too. So you should always consider using this binding approach first.
Update
Looks like the real issue is related to ElementName binding to an ancestor. But if you use the normal MVVM approach by specifying the DataContext like this -
MyExtraGridLines = new List<double>() { 10, 100, 1000 };
DataContext = this;
And removing the ElementName binding with a normal one -
<local:AxisBase ExtraGridLines="{Binding MyExtraGridLines}" />
Also make sure the axis element is in the visual tree by adding -
<UserControl x:Class="xxx.MyChart">
<Grid>
<ContentPresenter Content="{x:Bind BottomAxis, Mode=OneWay}" />
</Grid>
</UserControl>
This should work too.

How to create a Dependency Property for Binding

I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
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:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.

UserControl DataContext Binding

I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.

binding instanciated property to UI

I have this class :
public class property : DependencyObject, INotifyPropertyChanged
{
private string _myproperty;
public string MyProperty
{
get
{
return this._myproperty;
}
set
{
this._myproperty = value;
NotifyPropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string sproperty)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sproperty));
}
}
}
In the main window I have created an instance of this class myclass xx = new myclass();, where I populate my property with string data and bind it to XAML like so:
<Window.Resources>
<local:property x:Key="prop"></local:property>
</Window.Resources>
In my TextBox i have set the binding :
Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
This will not work unless if i use the existing resources:
var property = (local:property)Resources["prop"];
Is there another way to update the TextBox rather than using the resources? I want to use the normal class instantiation.
if you say Text="{Binding Path=MyProperty, Source={StaticResource prop}}" BorderBrush="#FFC7CACC" />
means that your VM is an instance of property class.
Try to surround your textbox with a Grid and set the grid dataContext with an instance of your poperty clas.
I mean
<Grid DataContext="from view or from behind assign your vm= new property()">
<TextBox Text="{Binding Path=MyProperty" ....../>
</Grid>
Try this:
<Window.DataContext>
<local:property/>
<Window.DataContext>
<TextBox Text="{Binding MyProperty}"/>
After setting the data context, just try to build the application, the build will succeed if it can find the property class in the local namespace.
After building your app, if succeeded, you can try to set the binding and also the Intellisense will automatically show MyProperty in Binding Options.
If this doesn't work, try to set the data context and binding using the Properties panel. Maybe visually you can get things right.
Try it, and if it fails, tell me where it went wrong

Bind a WPF View to couple of DataContexts

Objective: Set the visibility of a control based on the selected value of a ComboBox
Issue: The property that is being used to check the visibility is in the VM, however I don't know how to use it as DataContext is already defined to another object, i.e would I need to bind 2 datacontexts?!
Details:
I have a CustomControl that I load in my view associating to it a DataContext (a List of objects that is displayed as a grid:
<GUI:Counterparties_UserInputs x:Name="UserInputs" DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource counterpartiesDataView}}"/>
In that user control I have some StackPanel which visibility should be triggered based on the selection of a ComboBox:
<ComboBox ItemsSource="{Binding Source={StaticResource CounterpartyTypes}}" SelectedValue="{Binding SelectedCounterpartyType}"/>
<StackPanel Visibility="{Binding Path=SelectedCounterpartyType,Converter={StaticResource SelectedValueToVisible}}"/>
The issue I have is that the code behind is never hit as I don't find how to associate an "extra" DataContext to the view.
Here is my code behind:
public partial class Counterparties_UserInputs : UserControl
{
...
public Counterparties_UserInputs()
{
// this.DataContext = _cptyUserInputsVM;
_cptyUserInputsVM = new Counterparties_UserInputs_VM();
InitializeComponent();
}
}
And the ViewModel where the Property "SelectedCounterpartyType" is never hit:
public class Counterparties_UserInputs_VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _selectedCounterpartyType;
public string SelectedCounterpartyType
{
get
{
return _selectedCounterpartyType;
}
set
{
_selectedCounterpartyType = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCounterpartyType"));
}
}
}
}
I've seen that answer already but it's not exactly what I am doing... So would really appreciate your help! Thank you!

Categories