Binding to code behind from custom control - c#

I have a GridView that has several buttons. One of them is defined by the following template:
<DataTemplate x:Name="SubjectItemTemplate">
<Canvas Width="340" Height="170" VerticalAlignment="Top">
<Controls:ThreeImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding NormalImage}"
HoverStateImageSource="{Binding HoverImage}"
PressedStateImageSource="{Binding PressedImage}" Command="{Binding Path=NavigateToUnitsPage}"
CommandParameter="{Binding}" Canvas.Left="0" Canvas.Top="0">
</Controls:ThreeImageButton>
</Canvas>
</DataTemplate>
Now I have a custom control as you can see, called ThreeImageButton. The button works fine when I use it on its own. But when I have it in the DataTemplate it won't bind properties to the code behind.
Right now, I have
x:Name="MyThreeImageButton"
in the custom button definition. And I connect to the code-behind like this:
<TextBlock Text="{Binding ElementName=MyThreeImageButton, Path=NormalStateImageSource}"/>
(This is just a test to display the text, in the actual code I would assign an image source to another property that is referred to by an element).
Right now, nothing is displayed in the TextBlock. What is the correct binding syntax I'm supposed to use to reach my properties?
Thanks!
Edit: I am setting the variable in the InitializeComponent function and I am using SetValue on the DependencyProperty.
Edit: Let me add the following information to be more clear
Scenario I:
In DataTemplate for GridView:
<UserControl CustomParameter="Literal Text">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
works!
Scenario II:
In DataTemplate for GridView:
<UserControl CustomParameter="{Binding ValueFromDataItem">
In UserControl:
<TextBlock Text="{Binding CustomParameter}">
in UserControl .cs: this.DataContext = this
nope!

I see,
So setting up a two-way binding to a custom property in a user control can be tricky because a user control cannot bind to a CLR property. Not only that but setting the data context on a user control has an unexpected effect on the binding inside it.
You can solve these problems with a little slight of code. Basically back your CLR properties with dependency properties and set the data context on a child element instead of the root user control.
Take a look at this sample. Let's pretend you have the following MainPage. That MainPage will eventually use our custom user control. So let's set the stage.
Here's the code-behind:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new /* your view model */
{
Title = Guid.NewGuid().ToString(),
};
}
}
In the code above I am simulating a complex view model with a simple anonymous class. It would be silly for you to implement your own like this, but at the same time it is silly for me to build a simple sample with the complete scaffolding. I bring this up only so it does not confuse you - as it could look like I am suggesting this approach in prod.
Here's the XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:MyUserControl Text="{Binding Title}" />
</Grid>
In the XAML above, there is absolutely nothing special. I already have reference to the user control in the local namespace and I simply declare it here.
Okay, now that we have a consumer of the control, it's worth pointing out that in testing developers can mistakenly think that their binding is working because they test with literal values. Literal values bind fine. It's binding from the underlying view model that hick-ups.
Let's say another thing, some developers tend to avoid dependency properties because the require a little more typing. People remember that [kbd]propdp[/kbd] is a handy Visual Studio snippet that stubs out a dependency property for you.
Take a look at this user control. It has two controls, a TextBox and a TextBlock which are there to demonstrate the OneWay and TwoWay functionality of this binding approach. We also implement INotifyPropertyChanged on the user control. For the most part, adding a view model in the case of a user control is overkill because the user control already acts like a view model. It's up to the developer, but it seems dumb to me.
Here's the code behind:
public sealed partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public MyUserControl()
{
this.InitializeComponent();
}
// text property
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValueDp(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyUserControl), null);
// bindable
public event PropertyChangedEventHandler PropertyChanged;
void SetValueDp(DependencyProperty property, object value,
[System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
SetValue(property, value);
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In the ode above, I have create a "Text" property and backed it with a dependency property. For a matter of reuse I have also implemented SetValueDp() which could be used again and again if I had more than a single property. Even though this demo has but one, I wanted to include this because the repetitive logic should certainly be abstracted out like this.
Here's the XAML:
<Grid Background="Black" DataContext="{Binding ElementName=userControl}">
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
<TextBlock Text="{Binding Text}"
MinHeight="100" Padding="15" FontWeight="Light" FontSize="50" />
</StackPanel>
</Grid>
In the XAML above, I do nothing special insofar as binding. The syntax simply binds to the Text property using the Mode appropriate to the control. Just like you would do normally. However, what's worth noticing is that the DataContext is NOT set on the user control. Instead, it is set on the Grid. As a point of fact, any control in the tree other than the user control could be used like this. Just don't set the data context of the user control.
That is it by the way.
I have tested it to make sure it works. Demonstrating both one and two way binding is pretty handy here. I might even turn this into a blog in case other developers want to find it and don't discover this question. Thanks for your question!
Best of luck!

As the comments alluded to, your DataTemplate is placing the datacontext of the items to whatever object you are adding to your list. This is not the same as the surrounding user control's data context. If you want to reference that datacontext's commands, do the following in the DataTemplate's bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.NormalImage}
What this is saying is to go out and find the user control ancestor and use its datacontext and then look for the NormalImage property. If you run into problems, check your output window for binding errors. It is very helpful in finding binding problems.

Related

WPF MVVM Binding to parent view's ViewModel property

I have two views:
ShellView - DataContext: ShellViewModel
VolumeViewerView - DataContext: VolumeViewerViewModel
ShellView is the root in my program and it contains 3 VolumeViewerView objects (using their ViewModel instances).
Within each context, things run pretty smooth and as expected.
However, in the VolumeViewerView, I have an <Image> whose Cursor I'd like to control from the ShellViewModel.
Here is the code:
ShellView.xaml - MainView is an instance of VolumeViewerViewModel
<Frame Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" Content="{Binding MainView}"/>
ShellViewModel.cs
private Cursor _editorCursor;
public Cursor EditorCursor
{
get { return _editorCursor; }
set
{
_editorCursor = value;
NotifyOfPropertyChange(() => EditorCursor);
}
}
VolumeViewerView.xaml
<Image [OTHER PROPERTIES]
Cursor="{Binding Path=DataContext.EditorCursor,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:ShellView}}}"/>
When I bind the Cursor to a property in VolumeViewerViewModel things work well as expected. But here it isn't connecting.
I've looked for answers here, here, and here. But none of them is working.
I wonder, is this even possible given they are in different files? Does FindAncestor only work within the context of the same xaml file?
If it isn't possible, what is a good alternative? I can create EditorCursor inside VolumeViewerViewModel and have it set every time the EditorCursor in ShellViewModel is set, but that looks a bit ugly. If it is the only way, I may just elect to go there. But I would really love suggestions!
As #Clemens indicated in the comments, the Visual Tree ends with a Frame element. This was the root of my issues. I had put the child view in a Frame which meant FindAncestor was incapable of finding the parent element and its DataContext.
I edited ShellView.xaml, replaced Frame:
<ContentControl Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" Content="{Binding MainView}"/>
This meant that I also had to edit the child view (it originally inherited Page which cannot be the content of a ContentControl). I made it UserControl and the binding worked as expected.

UWP simple Databinding not working

I have been following some training videos and created a simple app with UWP and C#, and using UWP Databinding, unfortunately passing data from a textbox to a class property just doesn't work. Nothing happens. No data is passed, no errors generated.
So my class(everything stripped to relevant code) is
public class ChangeCalc
{
public string GoodsCost { get; set; }
public string Amountpaid { get; set; }
Under page is
<Page.DataContext>
<local:ChangeCalc />
</Page.DataContext>
Under my 2 TextBoxes I have
<TextBox
x:FieldModifier="public"
Text="{Binding Amountpaid, Mode=TwoWay}"
TextWrapping="Wrap" />
So it is all there.
If I just call it under the button Click event in the codebehind it works OK. Data passes, code runs.
myChangeCalc.GoodsCost = txtCost.Text;
myChangeCalc.Amountpaid = txtPaid.Text;
When is the databinding initiated? What event, if any, is missing?
Thanks for your help.
There is nothing being passed through. yet with the click event to the same properties, it passes OK.
This is because when you use data binding, you just give a data model as DataContext in where the property can be found to the TextBox, you didn't actually pass any real data to the TextBox. And when you use button click event, you create an instance "myChangeCalc" of this data model and pass a real data like "txtPaid.Text" to the property "Amountpaid" of this instance.
silverfighter, you don't have to implement INotifyPropertyChanged (what do I know I can't get it to work) The instructions say : Changes to TextBox.Text are sent to a two-way bound source when the TextBox loses focus, and not after every user keystroke.
Yes, but this change is from binding target to binding source, if you want change from binding source to binding target here, you must implement the INotifyPropertyChanged interface on the source object so that the source can report changes through events that the binding engine listens for.
So, as an example here:
<Page.Resources>
<local:ChangeCals x:Name="ccData" Amountpaid="111" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{StaticResource ccData}">
<TextBox
x:FieldModifier="public"
Text="{Binding Amountpaid, Mode=TwoWay}"
TextWrapping="Wrap" Height="50" />
</Grid>
You can create a instance of your data model in the page resources, and set this resource as the DataContext of Binding.
Another example:
<TextBox
x:FieldModifier="public"
Text="{Binding Amountpaid, Mode=TwoWay}"
TextWrapping="Wrap" />
code behind:
public MainPage()
{
this.InitializeComponent();
this.DataContext = myChangeCalc;
}
public ChangeCals myChangeCalc = new ChangeCals { Amountpaid = "111", GoodsCost = "222" };
Any way, the binding source should be an instance of your data model(ChangeCals class). Since you used {Binding} here, I didn't show sample of using {x:Bind}, if you want to learn more about {x:Bind}, you can refer to {x:Bind} markup extension. For more info about binding and x:Bind, you can refer to Data binding in depth.

Dependency Property Datacontext

I have a usercontrol, and there is a Datacontext set for it. This usercontrol contains also a Dependency-Property. Now, i want simply bind to this property.
I think the problem has something to do with the wrong datacontext.
The dependency-Property in my usercontrol (called TimePicker) looks like this:
public TimeSpan Time
{
get { return (TimeSpan)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
OnPropertyChanged();
}
}
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (TimePicker));
I try to use it like this:
<upDownControlDevelopement:TimePicker Grid.Row="1" Time="{Binding Path=TimeValue}" />
When i do this i get the following binding error:
System.Windows.Data Error: 40 : BindingExpression path error: 'TimeValue' property not found on 'object' ''TimePicker' (Name='TimePickerControl')'. BindingExpression:Path=TimeValue; DataItem='TimePicker' (Name='TimePickerControl'); target element is 'TimePicker' (Name='TimePickerControl'); target property is 'Time' (type 'TimeSpan')
Any help would be highly appreciated
Greetings Michael
PS: you can download the code at here
Although this has now been solved there seems to be some, in my opinion, inappropriate use of the DataContext.
When developing a custom reusable control, you should not set DataContext at all. What the DataContext will be, that is for the user of the control to decide, not for the developer. Consider the following common pattern of code:
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
</Grid>
Notice that here, you are using the Grid control. The developer of the control (in this case, the WPF team), didn't touch the DataContext at all - that is up to you. What does it mean for you as a control developer? Your DependencyProperty definition is fine, but you shouldn't touch the DataContext. How will you then bind something inside your control to the DependencyProperty value? A good way is using a template (namespaces omitted):
<MyTimePicker>
<MyTimePicker.Template>
<ControlTemplate TargetType="MyTimePicker">
<!-- Stuff in your control -->
<TextBlock Text="{TemplateBinding Time}" />
<TextBox Text="{Binding Time, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
<MyTimePicker.Template>
</MyTimePicker>
Note that TemplateBinding is always one-way only, so if you need any editing at all, you need to use normal binding (as you can see on the TextBox in the example).
This only means that the TextBlock/Box inside your control will get its Time value from your custom control itself, ignoring any DataContext you might have set.
Then, when you use the control, you do it like this (added to my first example):
<Grid DataContext="{Binding Data}">
<TextBox Text="{Binding TextValue1}" />
<!-- Some more controls -->
<MyTimePicker Time="{Binding TimeValue}" />
</Grid>
What just happened here is that the MyTimePicker does not have DataContext set anywhere at all - it gets it from the parent control (the Grid). So the value goes like this: Data-->(binding)-->MyTimePicker.Time-->(template binding)-->TextBlock.Text.
And above all, avoid doing this in the constructor of your custom control:
public MyTimePicker()
{
InitializeComponent();
DataContext = this;
}
This will override any DataContext set in XAML, which will make binding a huge pain (because you'll have to always set Source manually). The previous example would not work, and this wouldn't work either:
<MyTimePicker DataContext="{Binding Data}" Time="{Binding TimeValue}" />
You would think this is OK, but the DataContext will be resolved in the InitializeComponent() call, so the value will be immediately overwritten. So the binding to TimeValue will look for it in the control instead (which will, of course, fail).
Just don't touch the DataContext when developing a control and you'll be fine.
You don't need to override the data context of user control. You can use RelativeSource to point your binding source property i.e. TimeValue to any other source you like. E.g. If you have the source property in your window's class. You could simply point your binding target to the source in window's data context as follows:
{Binding Path=DataContext.TimeValue, RelativeSource={ RelativeSource AncestorType={x:Type Window}}}
Your error states that 'TimeValue' property not found on 'object' 'TimePicker', which means that the WPF Framework is looking at the 'TimePicker' object to resolve the 'TimeValue' property value. You must have somehow set the DataContext of the Window or UserControl that contains the 'TimePicker' object to an instance of the 'TimePicker' object.
Instead, it should be set to an instance of the class that declares the 'TimeValue' property. If you're using a view model, then you should set it to an instance of that:
DataContext = new YourViewModel();
If the 'TimeValue' property is declared in the Window or UserControl then you can set the DataContext to itself (although generally not recommended):
DataContext = this;
Please note that when data binding to the 'Time' property from inside your TimePicker control, you should use a RelativeSource Binding:
<TextBlock Text="{Binding Time, RelativeSource={RelativeSource
AncestorType={x:Type YourLocalPrefix:TimePicker}}}" ... />
Normally we are not setting datacontext directly.If u want to set datacontext create an instance of your usercontrol and set datacontext individually to each one.

In my C# XAML Windows 8.1 app, how can I iterate over my ListView?

I'm new to Windows 8.1 development, XAML, and C#, so if this question is rudimentary, please forgive me.
I've got a <Page> in my app that contains a <ListView>, like so:
<ListView ItemsSource="{Binding Mode=TwoWay}" x:Name="ListView_Statistical">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Style="{StaticResource SubheaderTextBlockStyle}" Width="100" Margin="10,20">
<Run Text="X/Y " />
<!--<Run Text="{Binding Source={StaticResource ThisPage}, Path=i}" />-->
</TextBlock>
<TextBox HorizontalAlignment="Left" Text="{Binding xVal}" PlaceholderText="X" InputScope="Number" FontSize="28" Width="100" Margin="0,10,10,10" />
<TextBox HorizontalAlignment="Left" Text="{Binding yVal}" PlaceholderText="Y" InputScope="Number" FontSize="28" Width="100" Margin="0,10,10,10" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the Code Behind, I set up its DataContext like so:
ListView_Statistical.DataContext = this.statisticalPoints;
this.statisticalPoints is defined as such:
public ObservableCollection<StatisticalPoint> statisticalPoints
{
get { return (ObservableCollection<StatisticalPoint>)GetValue(statisticalPointsProperty); }
set {
SetValue(statisticalPointsProperty, value);
NotifyPropertyChanged("statisticalPoints");
}
}
// Using a DependencyProperty as the backing store for statisticalPoints. This enables animation, styling, binding, etc...
public static readonly DependencyProperty statisticalPointsProperty =
DependencyProperty.Register("statisticalPoints", typeof(ObservableCollection<StatisticalPoint>), typeof(EnterCalc), new PropertyMetadata(0));
I'm not sure if making this a DependencyProperty is necessary, or if making it follow INotifyPropertyChanged is necessary, but they don't seem to hurt.
Anyway, so in my constructor, I add a bunch of stuff to my statisticalPoints:
this.statisticalPoints = new ObservableCollection<StatisticalPoint>();
this.statisticalPoints.Add(new StatisticalPoint() { xVal = 1.0, yVal = 2.0 });
this.statisticalPoints.Add(new StatisticalPoint() { xVal = 33.0, yVal = 44.0 });
this.statisticalPoints.Add(new StatisticalPoint() { xVal = 555.0, yVal = 666.0 });
this.statisticalPoints.Add(new StatisticalPoint() { xVal = 0.7, yVal = 0.8 });
And when I load up the page, I do indeed see five rows in my ListView, populated as defined in my initialization of this.statisticalPoints.
The part I'm having trouble with is this:
I change the first value in the first <TextBox> in the ListView, then hit my save button... but ListView.Items doesn't have my change reflected, and I can't figure out how to look at the <TextBox> itself.
What I really want to do is have this bunch of statistical points modifiable by my user and be able to save their changes. To do that, I feel like I need to read the value in the <TextBox>es, but I can't figure out how to do that.
Alternatively, if the 'right way' to do this is to keep the data in this.statisticalPoints up-to-date when changes are made in the <TextBox>es, then I thought that a TwoWay binding Mode would do it, but neither ListView.Items nor this.statisticalPoints is changed when I make changes in the <TextBox>.
I do not have event handlers set up in those <TextBox> elements, as you can see, but do I need them, or am I missing something obvious?
Thanks in advance for any help you can give me!
To solve your initial problem, make the binding for each text box Mode="TwoWay". For reasons beyond my comprehension, the mode is default OneWay on pretty much everything in Windows Store Apps.
Making the ItemsSource binding two way does next to nothing, as the UI isn't changing the collection itself (by changing, I mean completely replacing). To iterate over your collection, just iterate over this.statisticalPoints and it will have the current data.
Now, you have a ton of other misconceptions so to try and run through them:
You never showed your save button, but the bindings either update your source or they don't. A save button is usually used to persist changes from the view model to the model.
Speaking of view models, you don't appear to have one. You shouldn't be directly setting the data context of controls, and certainly shouldn't have so much in code-behind. Create a proper view model object for your page, and bind ItemsSource to a public property of that view model.
NotifyPropertyChanged on a collection usually is unnecessary unless you are replacing the collection in code.
Having it won't hurt though, except, the setter of the backing property of a DependencyProperty (DP) is never called by the framework, so putting it there is just weird
And you don't really need a DP at all. DPs are there so a parent control can bind data to your special user control. Until you are using user controls, and really understand how DPs work, you shouldn't need to use them.

WPF binding view data doesn't update correctly

I have this code part:
<TextBlock
Margin="5,3,5,1" Foreground="White"
FontWeight="Bold" FontStyle="Italic" TextAlignment="Center"
Text="{Binding AntennaName}"/>
and in my viewmodel:
private string antennaName;
public string AntennaName
{
get { return antennaName; }
set { antennaName = value; OnPropertyChanged("AntennaName"); }
}
I checked and I can confirm that in my actual code the AntennaName property does change but the textblock does not.
Can anyone please explain why is this happening? I'm pretty new to the mvvm scene.
Try this -
<TextBlock Text="{Binding DataContext.AntennaName,
RelativeSource={RelativeSource FindAncestor,
AncestorType=UserControl}}"/>
The problem somewhere lies in the way you are setting the DataContext for your UserControl. Somehow, textBlock is not inheriting the DataContext from its parent(UserControl). So, explicitly asking for it might work.
Explanation
UI elements by default search for the Binding in its DataContext unless explicitly specified to look into some other place.
Also, in case you haven't set the DataContext for the control, it will inherit DataContext from its parent Control and look for the Binding property in it. In case the binding property is not found on the parent DataContext either, binding fails silently and all you will see is empty string.
You can always look for Binding failures in the output window. If you look in the output window, you will see your property AntennaName over there.
Refer - Data Binding Overview

Categories