I'm building a simple UserControl, DoubleDatePicker, which defines a DependencyProperty, SelectedDate :
DoubleDatePicker.xaml :
<UserControl x:Class="TestWpfDoubleDatePicker.DoubleDatePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit">
<StackPanel x:Name="LayoutRoot" Background="White">
<toolkit:DatePicker x:Name="DateInput" SelectedDate="{Binding SelectedDate,Mode=TwoWay}" Margin="5,0,5,0" />
<TextBlock Text="{Binding SelectedDate}" />
<toolkit:DatePicker SelectedDate="{Binding SelectedDate,Mode=TwoWay}" Margin="5,0,5,0" />
</StackPanel>
DoubleDatePicker.xaml.cs :
using System;
using System.Windows;
using System.Windows.Controls;
namespace TestWpfDoubleDatePicker
{
public partial class DoubleDatePicker : UserControl
{
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate", typeof(DateTime), typeof(DoubleDatePicker), null);
public DateTime SelectedDate
{
get { return (DateTime)this.GetValue(SelectedDateProperty); }
set { this.SetValue(SelectedDateProperty, value); }
}
public DoubleDatePicker()
{
this.InitializeComponent();
this.DataContext = this;
}
}
}
I'd like to be able to bind the SelectedDate property from the outside but things do not seem so simple.
Here is a sample code that is trying to get the value of the property in a TextBlock :
MainWindow.xaml :
<Window x:Class="TestWpfDoubleDatePicker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfDoubleDatePicker"
Title="MainWindow" Height="350" Width="525">
<StackPanel x:Name="LayoutRoot" Background="White">
<local:DoubleDatePicker x:Name="ddp" SelectedDate="{Binding SelectedDate}" />
<Button Content="Update" Click="Button_Click" />
<TextBlock Text="{Binding SelectedDate}" />
</StackPanel>
and MainWindow.xaml.cs :
using System;
using System.Windows;
namespace TestWpfDoubleDatePicker
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public static readonly DependencyProperty SelectedDateProperty =
DependencyProperty.Register("SelectedDate", typeof(DateTime), typeof(MainWindow), null);
public DateTime SelectedDate
{
get { return (DateTime)this.GetValue(SelectedDateProperty); }
set { this.SetValue(SelectedDateProperty, value); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedDate = this.ddp.SelectedDate;
}
}
}
Inside the DoubleDatePicker itself all is working fine : the SelectedDate property is updated when changed by using any of the two DatePicker, and the TextBlock of the DoubleDatePicker is updated as expected.
But, outside, the TextBlock of the MainWindow is not updated automatically and the only way to get the SelectedDate property of the DoubleDatePicker is to get it explicitly, like it's done when clicking the Button.
What am I doing wrong ?
I'm using Visual Studio Professional 2010 with WPF 4.
Thanks in advance for you help.
What you are doing wrong is overwriting your DataContext inside your control with this:
this.DataContext = this;
Now your DatePicker no longer binds to your intended object, but in stead binds to your DatePicker instance. I guess this is not how you intended your DatePicker to work ;).
So, remove that line in your DatePicker, and if you do need to bind inside the XAML of your DatePicker use ElementName or RelativeSource bindings to bind to this dependency property.
Hope this clarifies things ;)
I took the liberty of rewriting your bindings inside your XAML of your DatePicker using ElementName bindings:
<UserControl x:Class="TestWpfDoubleDatePicker.DoubleDatePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
x:Name="Root">
<StackPanel x:Name="LayoutRoot" Background="White">
<toolkit:DatePicker x:Name="DateInput" SelectedDate="{Binding ElementName=Root, Path=SelectedDate,Mode=TwoWay}" Margin="5,0,5,0" />
<TextBlock Text="{Binding ElementName=Root, Path=SelectedDate}" />
<toolkit:DatePicker SelectedDate="{Binding ElementName=Root, Path=SelectedDate,Mode=TwoWay}" Margin="5,0,5,0" />
</StackPanel>
Related
I'm writing a small UWP project also using the MVVM pattern (MVVM Light framework) for my studies and encountered a binding issue in numerous scenarios and controls.
I've taken the effort to isolate one of those into mini example project available here.
The IsActive property of ProgressRing control is not reacting on updates from ViewModel side even through it has:
INotifyPropertyChanged implemented
Binding mode explicitly set to TwoWay binding
UpdateSourceTrigger explicitly set to PropertyChanged
MainPage.xaml
<Page
x:Class="TestProgressRing.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:TestProgressRing"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="491.282"
Height="492.308"
DataContext="{Binding MainPageViewModel, Source={StaticResource ResourceKey=Locator}}"
mc:Ignorable="d">
<Grid
Width="500"
Height="800"
Margin="0,0,-8.667,-307.667"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="3*" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Width="250"
Height="50"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Boom">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:InvokeCommandAction Command="{Binding ClickCommand}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<ProgressRing
Grid.Row="1"
Width="500"
Height="500"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsActive="{Binding IsLoading, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Visibility="Visible" />
</Grid>
</Page>
MainPageViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace TestProgressRing
{
public class MainPageViewModel : ViewModelBase
{
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set
{
_isLoading = value;
Set(() => IsLoading, ref _isLoading, value);
}
}
public ICommand ClickCommand { get; set; }
public MainPageViewModel()
{
ClickCommand = new RelayCommand(() =>
IsLoading = !IsLoading);
}
}
}
Does the UWP controls just simply doesn't works well with regular Binding instead of x:Bind? There are more issues like that for example CalendarPickerView Date property is also don't want to cooperate.
First, you don't need UpdateSourceTrigger=PropertyChanged, Mode=TwoWay in your binding. It's always going to be source to target (i.e. OneWay).
The real issue is that you shouldn't be setting _isLoading = value; since it's passed in as a ref for checking if property value has been changed or not.
So deleting this line will make your code work. You can even simplify it to
private bool _isLoading;
public bool IsLoading
{
get => _isLoading;
set => Set(ref _isLoading, value);
}
Try changing your binding to:
IsActive="{Binding IsLoading}"
And your property code to:
private bool _isLoading;
public bool IsLoading
{
get {return _isLoading;}
set
{
_isLoading = value;
NotifyOfPropertyChange(nameof(IsLoading));
}
}
I have used implementation of INotifyPropertyChanged from Caliburn.Micro framework, but it can be any other implementation of it that works ;)
I have a user control like so:
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding NameC}" Width="100" />
<TextBlock Text="{Binding Filename}" />
</StackPanel>
</Grid>
with DP in code behind:
public TestUc()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty NameCProperty = DependencyProperty.Register(
"NameC", typeof(string), typeof(TestUc), new PropertyMetadata(default(string)));
public string NameC
{
get { return (string) GetValue(NameCProperty); }
set { SetValue(NameCProperty, value); }
}
public static readonly DependencyProperty FilenameProperty = DependencyProperty.Register(
"Filename", typeof (string), typeof (TestUc), new PropertyMetadata(default(string)));
public string Filename
{
get { return (string) GetValue(FilenameProperty); }
set { SetValue(FilenameProperty, value); }
}
Now, when using it in a window,
this works fine:
<Window x:Class="TestDpOnUc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:uc="clr-namespace:TestDpOnUc"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<uc:TestUc NameC="name is xxx" Filename="This is filename" />
</Grid>
But This does not:
<Window x:Class="TestDpOnUc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:uc="clr-namespace:TestDpOnUc"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<uc:TestUc NameC="{Binding Name}" Filename="{Binding FileName}" />
</Grid>
public MainWindow()
{
InitializeComponent();
DataContext = this;
Name = "name is nafsafd";
FileName = "lkjsfdalkf";
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged();
}
}
private string _FileName;
public string FileName
{
get { return _FileName; }
set
{
_FileName = value;
OnPropertyChanged();
}
}
Can someone please explain why? Why is the datacontext of the user control not automatically set to the parent - the main window?
It's because of this line DataContext = this in UserControl constructor. You set DataContext to your user control which affects default binding context for TestUc and all children, including <uc:TestUc ... />. So at the moment
<uc:TestUc NameC="{Binding Name}" Filename="{Binding FileName}" />
will look for Name and FileName properties inside UserControl. You need to remove that line but that will break bindings within the user control.
<TextBlock Text="{Binding NameC}" Width="100" />
<TextBlock Text="{Binding Filename}" />
Will look for NameC and Filename in MainWindow. Solution to that is to change binding context, per binding, via either RelativeSource or ElementName binding inside UserControl
<UserControl ... x:Name="myUserControl">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding ElementName=myUserControl, Path=NameC}" Width="100" />
<TextBlock Text="{Binding ElementName=myUserControl, Path=Filename}" />
</StackPanel>
</Grid>
</UserControl>
When creating a UserControl with DependencyProperties you have to bind your DP's within your UserControl with ElementName- or RelativeSource Binding to your Controls in your UserControl
<TextBlock Text="{Binding ElementName=myUserControl, Path=NameC}" Width="100" />
and you never set the DataContext.
DataContext = this; <-- do not do that within your UserControl
When you wanna use your UserControl you put it in your View and bind the Properties of your actual DataContext/Viewmodel to the DependencyProperties of the UserControl.
<uc:TestUc NameC="{Binding Name}" Filename="{Binding FileName}" />
Whe you are doing <uc:TestUc NameC="{Binding Name}" Filename="{Binding FileName}" /> its not looking at MainWindow's data context instead at your UserControl's data context.
So you might want to search for the right element and bind it. Below is one way to do it using ElementName by giving a name to Window like MainWindowName. Or you can also use relative source to search for its ancestor.
<Window x:Class="TestDpOnUc.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:uc="clr-namespace:TestDpOnUc"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
x:Name="MainWindowName">
<Grid>
<uc:TestUc NameC="{Binding Element=MainWindowName, Path=DataContext.Name}" Filename="{Binding Element=MainWindowName, Path=DataContext.FileName}" />
</Grid>
I have usercontrol which has datagrid .This usercontrol is added to WPF main window.I am handling gridrow selection changed event through bubble event.
<ListBox x:Name="myListBox" Grid.Row="0"
ItemsSource="{Binding Path=_myControl}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectedItem="{Binding CurrentItem}" SelectedIndex="1">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<local:UCSearchEntity GridRowSelectionConfirmed="{Binding Path=UCSearchEntity_GridRowSelectionConfirmed}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class MyViewModel:INotifyPropertyChanged
{
}
the error is Provide value on 'System.Windows.Data.Binding' threw an exception.
How can I access this usercontrol event in my mainwindow viewModel ?
You cannot do binding to events like that you have to do something like this on your mainwindow :
<Window DataGrid.GridRowSelectionConfirmed="GridRowSelectionConfirmed">
and GridRowSelectionConfirmed would be a method in your mainwindow
And the xaml above is a snippet in your xaml of the mainwindow.
If you want to stick to using MVVM then you have to start using behaviours but this is a more advanced concept. The behaviour is needed to attach a command that you can databind to an event that otherwise is not bindable like you were trying to do. You see I am making use of interactivity, if you want to do the same you need the blend sdk. An example :
public class AddingNewItemBehavior : Behavior<DataGrid>
{
public static readonly DependencyProperty CommandProperty
= DependencyProperty.Register("Command", typeof(ICommand), typeof(AddingNewItemBehavior), new PropertyMetadata());
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.AddingNewItem += AssociatedObject_OnAddingNewItem;
}
private void AssociatedObject_OnAddingNewItem(object sender, AddingNewItemEventArgs addingNewItemEventArgs)
{
AddingNewItem addingNewItem = new AddingNewItem();
Command.Execute(addingNewItem);
addingNewItemEventArgs.NewItem = addingNewItem.NewItem;
}
}
This is an adding new behaviour I have on a datagrid.
And this is a simplified example where i make use of that behaviour :
<UserControl x:Class="Interstone.Configuratie.Views.GraveerFiguurAdminUserControl"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:iCeTechControlLibrary="clr-namespace:ICeTechControlLibrary;assembly=ICeTechControlLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<DataGrid ItemsSource="{Binding ZandstraalImageTypes.View}" AutoGenerateColumns="False"
VerticalGridLinesBrush="#FFC9CACA" HorizontalGridLinesBrush="#FFC9CACA" RowHeaderWidth="50"
>
<i:Interaction.Behaviors>
<iCeTechControlLibrary:AddingNewItemBehavior Command="{Binding AddingNewCommand}"/>
</i:Interaction.Behaviors>
<DataGrid.Columns>
<DataGridTextColumn Header="Categorie" Binding="{Binding TypeNaam}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
I have created a very simple user control, an ImageButton
<UserControl x:Class="SampleApp.Controls.ImageButton"
Name="ImageButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Button>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="6*" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Image Grid.Row="1" Source="{Binding ElementName=ImageButtonControl, Path=Image}" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
<TextBlock Grid.Row="2" Text="{Binding ElementName=ImageButtonControl, Path=Text}" VerticalAlignment="Stretch" HorizontalAlignment="Center" />
</Grid>
</Button>
</UserControl>
With code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SampleApp.Controls
{
/// <summary>
/// Interaction logic for ImageButton.xaml
/// </summary>
public partial class ImageButton : UserControl
{
public ImageButton()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButton), new UIPropertyMetadata(""));
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
}
}
Now I want to use that in my little sample application like
xmlns:controls="clr-namespace:SampleApp.Controls"
<controls:ImageButton Grid.Row="1"
Grid.Column="1"
Margin="2"
Image="/Images/link.png"
Text="DoSomething">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="DoSomething" />
</i:EventTrigger>
</i:Interaction.Triggers>
</controls:ImageButton>
If I give it a x:Name, like
<controls:ImageButton x:Name="DoSomething"
e.g. DoSomething the method DoSomething with that name is directly called when the view is shown, i.e. when I active the viewmodel that contains that button, just like I click the Button (if it was a normal button and not a usercontrol, it would work that way), but the button-click handler is never called on clicking.
Now I tried to add an ActionMessage as seen above, but it does not work either...
What is wrong here?
That's because there is no convention configured for your user control type. You could either add a convention via the ConventionManager (see http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions), or you could derive your type from Button instead.
You could also not use a custom user control and instead just add the image to the Content property of the Button in your view.
I have a:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'ElementName=Test'. BindingExpression:Path=Value;
DataItem=null; target element is 'Slider' (Name=''); target property
is 'Value' (type 'Double')
error in a very special case.
I though about a name scope problem but I don't know how to fix it.
Consider the following WPF application :
MyUserControl.xaml:
<UserControl x:Class="WpfApplication1.MyUserControl"
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" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="Move me !"/>
<Slider x:Name="Test" Grid.Row="0" Grid.Column="1" />
<Label Grid.Row="1" Content="Binded slider"/>
<ContentPresenter Grid.Row="1" Grid.Column="1">
<ContentPresenter.Content>
<Slider Value="{Binding Value, ElementName=Test}"/>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</UserControl>
MyUserControl.xaml.cs:
using System.Windows.Controls;
namespace WpfApplication1
{
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300" Height="120">
<StackPanel>
<Button Click="Button_Click" Content="Click me to show Sliders !" Height="25"/>
<ContentPresenter x:Name="contentPresenter"/>
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private MyUserControl myUserControl;
public MainWindow()
{
InitializeComponent();
myUserControl = new MyUserControl();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
contentPresenter.Content = myUserControl;
}
}
}
Given the code of MyUserControl.xaml, I expect the binded slider to have the same value has the first one.
But nothing happens.
Now, the tricky part: Start the application, Click on the button, Move the first slider, Open "WPF Inspector" and Attach it to the application. Result: The binding is fixed.
How do you explain this phenomenon?
This will do the trick
<!--<ContentPresenter Grid.Row="1" Grid.Column="1">
<ContentPresenter.Content>-->
<Slider Value="{Binding Value, ElementName=Test}" Grid.Column="1" Grid.Row="1"/>
<!--</ContentPresenter.Content>
</ContentPresenter>-->
Because Slider is inside a content presenter you can't access it's value like that. You would need to bind to the same viewmodel property to achive this while having it in the content presenter.
Edit
Get Prism and user a ViewModel... I won't go into details... But What you need to do is this...
After you donwloaded everything add the Microsoft.Practices.Prism dll to your project
Create a new class (ViewModel).Name it whatever you want. I call my MyUserControlViewModel and make it look like this
using Microsoft.Practices.Prism.ViewModel;
namespace YourNamespace
{
public class MyUserControlViewModel : NotificationObject
{
private double _sliderValue;
public double SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
RaisePropertyChanged(() => SliderValue);
}
}
}
}
Change the XAML in your usercontrol like this
<Slider x:Name="Test" Grid.Row="0" Grid.Column="1" Value="{Binding SliderValue, Mode=TwoWay}"/>
<ContentPresenter Grid.Row="1" Grid.Column="1">
<ContentPresenter.Content>
<Slider Value="{Binding SliderValue}"/>
</ContentPresenter.Content>
</ContentPresenter>
And add this line of code into your MyUserControl.cs file
DataContext = new MyUserControlViewModel();
Now the ViewModel takes over for the heavy lifting... You bound the value of the first slider to the Property SliderValue (the Mode=TwoWay indicates that this control can set and get the state of the property) and you have bound the other slider to the same SliderValue so that it moves in sync with the first
Hope this helps