I am having a hard time understanding the CommandTarget property for a RoutedCommand.
Basically, I have some static commands that have implementations in a user control (not the window). I create a commandbinding in the user control. If I declare the button in the usercontrol, then I am able to use my routed event. However, when the button is outside of the usercontrol, then I cannot use my routed event. I think the command target will solve my issue.
So how do I set the commandtarget for the toolbar usercontrol's button, so that the Container's Executed and CanExecuted is called?
Edited Code with changes from micahtan changes, but I still can't get it to CanExecute or Execute.
Window XAML:
<Window x:Class="RoutedCommands.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedCommands"
xmlns:toolbar="clr-namespace:RoutedCommands.Toolbar"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:Container Width="100" Height="25" x:Name="MyContainer" />
<toolbar:Toolbar Width="100" Height="25" CommandTarget="{Binding MyContainer}" />
</StackPanel>
</Window>
Toolbar XAML:
<UserControl x:Class="RoutedCommands.Toolbar.Toolbar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedCommands"
x:Name="MyToolbar"
Height="300" Width="300">
<Grid>
<Button Command="{x:Static local:Commands.MyCommand}" Content="Try Me" CommandTarget="{Binding ElementName=MyToolbar, Path=CommandTarget, Mode=OneWay}" />
</Grid>
</UserControl>
Toolbar CS:
public partial class Toolbar : UserControl
{
public Toolbar()
{
InitializeComponent();
}
// Using a DependencyProperty as the backing store for CommandTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(Toolbar), new UIPropertyMetadata(null));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
}
Container XAML:
<UserControl x:Class="RoutedCommands.Container"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RoutedCommands"
Height="300" Width="300">
<UserControl.CommandBindings>
<CommandBinding Command="{x:Static local:Commands.MyCommand}" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
</UserControl.CommandBindings>
<Grid>
<Button Command="{x:Static local:Commands.MyCommand}" Content="Click Me" />
</Grid>
</UserControl>
Container CS:
public partial class Container : UserControl
{
public Container()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
Console.WriteLine("My Command Executed");
}
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
Console.WriteLine("My Command Can Execute");
e.CanExecute = true;
}
}
RoutedCommands:
namespace RoutedCommands
{
public static class Commands
{
public static readonly RoutedUICommand MyCommand = new RoutedUICommand();
}
}
If you want to use CommandTargets, I would create a CommandTarget DependencyProperty on your custom UserControl, similar to the way it's defined on ButtonBase.
After doing that, set your Button's CommandTarget to your custom UserControl's CommandTarget.
EDIT: Code Sample
Rudi's comments are valid if you're doing an MVVM architecture -- RelayCommands or some other form of wrapped delegates work well in that case. Based on your code sample, it didn't look like you were using that approach, hence my original comment.
As for the code, you only need to change your ToolBar class. This assumes your MyCommand class inherits from RoutedUICommand. Here's the XAML:
<UserControl
x:Class="WPFCommandTarget.CustomToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFCommandTarget"
x:Name="theControl">
<Grid>
<Button
x:Name="theButton"
Command="{x:Static local:Commands.MyCommand}"
CommandTarget="{Binding ElementName=theControl, Path=CommandTarget, Mode=OneWay}"
Content="Try Me" />
</Grid>
</UserControl>
And here's the code-behind:
using System.Windows;
using System.Windows.Controls;
namespace WPFCommandTarget
{
/// <summary>
/// Interaction logic for CustomToolBar.xaml
/// </summary>
public partial class CustomToolBar : UserControl
{
public CustomToolBar()
{
InitializeComponent();
}
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
// Using a DependencyProperty as the backing store for CommandTarget. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CustomToolBar), new UIPropertyMetadata(null));
}
}
Please note that I've changed some of the class names/namespaces in my test project. You'll have to change them to suit your needs.
Have you concidered to rather use the RelayCommand or DelegateCommand! Thy might be more suited to what you need?
For a example of using RelayCommand, read this article by Josh
Brian Noyes also have a excellent article available here
Related
I have an ItemsRepeater on the page's XAML code where it's ItemsSource property is bind to a list of User Control (ObersvableCollection), a custom control I made. In this User Control there's a button that I wish would open a SplitView pane that I set in the Page's Xaml code. I'm thinking I need to get an instance of the page in the User Control's code behind, on the click event, but I have no idea how.
You can do it this way.
TestUserControl.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Windows.Input;
namespace WinUI3App1;
public sealed partial class TestUserControl : UserControl
{
public static readonly DependencyProperty ClickCommandProperty = DependencyProperty.Register(
nameof(ClickCommand),
typeof(ICommand),
typeof(TestUserControl),
new PropertyMetadata(null));
public TestUserControl()
{
InitializeComponent();
}
public ICommand ClickCommand
{
get => (ICommand)GetValue(ClickCommandProperty);
set => SetValue(ClickCommandProperty, value);
}
}
TestUseControl.xaml
<UserControl
x:Class="WinUI3App1.TestUserControl"
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"
mc:Ignorable="d"
x:Name="ThisControl">
<StackPanel Orientation="Horizontal">
<Button Command="{x:Bind ClickCommand}" CommandParameter="{Binding ElementName=ThisControl}" Content="Click" />
</StackPanel>
</UserControl>
MainWindow.xaml.cs
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WinUI3App1;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Install the CommunityToolkit.Mvvm NuGet package
// to avoid implementing commands yourself.
ClickCommand = new RelayCommand<TestUserControl>(OnClick);
for (int i = 0; i < 10; i++)
{
TestUserControls.Add(new TestUserControl()
{
ClickCommand = ClickCommand
});
}
}
public ObservableCollection<TestUserControl> TestUserControls { get; set; } = new();
public ICommand ClickCommand { get; set; }
private void OnClick(TestUserControl? sender)
{
SplitViewControl.IsPaneOpen = true;
}
}
MainWindow.xaml
<Window
x:Class="WinUI3App1.MainWindow"
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"
mc:Ignorable="d">
<SplitView x:Name="SplitViewControl">
<SplitView.Pane>
<Grid/>
</SplitView.Pane>
<StackPanel Orientation="Vertical">
<ItemsRepeater ItemsSource="{x:Bind TestUserControls}" />
</StackPanel>
</SplitView>
</Window>
I have a problem with binding in usercontrol.
This is my usercontrol:
UserControl1.xaml
<UserControl x:Class="WpfApp1.UserControl1"
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-ompatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
x:Name="usercontrol"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBox Text="{Binding HmiField, ElementName=usercontrol}"/>
</Grid>
</UserControl>
UserControl1.xaml.cs
namespace WpfApp1
{
public partial class UserControl1 : UserControl
{
public double HmiField
{
get { return (double)GetValue(HmiFieldProperty); }
set { SetValue(HmiFieldProperty, value); }
}
public static readonly DependencyProperty HmiFieldProperty =
DependencyProperty.Register("HmiField", typeof(double), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
}
}
And this is the main window:
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
DataContext="{Binding Md, RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<UniformGrid>
<Button Content="{Binding Prop1}" Click="Button_Click"/>
<Label Content="{Binding Prop1}"/>
<TextBox Text="{Binding Prop1}"/>
<local:UserControl1 HmiField="{Binding Prop1}"/>
</UniformGrid>
</Window>
MainWindow.xaml.cs
namespace WpfApp1
{
public class tMd: INotifyPropertyChanged
{
#region Interfaccia INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
#endregion
private double prop1;
public double Prop1 { get
{
return prop1;
}
set
{
if (prop1 != value)
{
prop1 = value;
NotifyPropertyChanged("Prop1");
}
}
}
}
public partial class MainWindow : Window
{
public tMd Md
{
get { return (tMd)GetValue(MdProperty); }
set { SetValue(MdProperty, value); }
}
public static readonly DependencyProperty MdProperty =
DependencyProperty.Register("Md", typeof(tMd), typeof(MainWindow), new PropertyMetadata(new tMd()));
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Md.Prop1 = 1234.5678;
}
}
}
I found some similar question:
How do I change TextBox.Text without losing the binding in WPF?
WPF: Binding is lost when bindings are updated
WPF Textbox TwoWay binding in datatemplate not updating the source even on LostFocus
But I can't completely understand what's happening: why a standard textbox work as expected and my usercontrol no?
Or better: is there a way to have my usercontrol works with the textbox's behaviour?
The Binding must be TwoWay, either set explicitly
<local:UserControl1 HmiField="{Binding Prop1, Mode=TwoWay}"/>
or implicitly by default:
public static readonly DependencyProperty HmiFieldProperty =
DependencyProperty.Register(
nameof(HmiField), typeof(double), typeof(UserControl1),
new FrameworkPropertyMetadata(
0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
A TextBox's Text property is registered like shown above, i.e. with the BindsTwoWayByDefault flag.
At the TextBox Binding in the UserControl's XAML you may also want to update the source property while the user is typing (instead of only on lost focus):
<TextBox Text="{Binding HmiField,
ElementName=usercontrol,
UpdateSourceTrigger=PropertyChanged}"/>
or without the otherwise useless generated usercontrol field:
<TextBox Text="{Binding HmiField,
RelativeSource={RelativeSource AncestorType=UserControl}
UpdateSourceTrigger=PropertyChanged}"/>
Your Prop1 notifies when it changes, but you haven't told you binding to trigger on that notification.
Try including UpdateSourceTrigger=PropertyChanged in you binding
I cannot get binding to work in my app with a user control. It is a UWP app with template 10.
I use the same bind in the mainpage as I do in the user control but the field in the user control does not react to changes. I have read several articles that tell me how to set the datacontent of my user control but I can not get any of them to work.
My code is as follows:
Mainpage.xaml
Page x:Class="UserControlTest.Views.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:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UserControlTest.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UserControlTest.ViewModels" mc:Ignorable="d">
<Page.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<controls:PageHeader x:Name="pageHeader" Content="Main Page"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignTopWithPanel="True" />
<TextBlock x:Name="mainTextBlock" Margin="16,16,16,16"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="pageHeader"
Text="{Binding TextToShow}" />
<local:ShowText Margin="16,16,16,16"
RelativePanel.Below="pageHeader"
RelativePanel.AlignRightWithPanel="True"/>
<Button Content="Change Text" Margin="16,16,16,16"
Command="{x:Bind ViewModel.ChangeText }"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="mainTextBlock"/>
</RelativePanel>
</Page>
MainPageViewModel.cs
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Security.Cryptography.Core;
using Template10.Mvvm;
using Template10.Services.NavigationService;
using Windows.UI.Xaml.Navigation;
namespace UserControlTest.ViewModels
{
public class MainPageViewModel : ViewModelBase
{
private string _textToShow = "Initial text";
public string TextToShow
{
get { return _textToShow; }
set { Set(ref _textToShow, value); }
}
DelegateCommand _changeText;
public DelegateCommand ChangeText
=> _changeText ?? (_changeText = new DelegateCommand(ExecuteChangeTest, CanChangeText));
private void ExecuteChangeTest()
{
TextToShow = "Changed text";
}
private bool CanChangeText()
{
return true;
}
}
}
ShowText.xaml
<UserControl
x:Class="UserControlTest.Views.ShowText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UserControlTest.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UserControlTest.ViewModels" mc:Ignorable="d">
<UserControl.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</UserControl.DataContext>
<Grid>
<TextBlock x:Name="UserControlTextBlock"
Text="{Binding TextToShow}" />
</Grid>
</UserControl>
At first glance the problem is that your UserControl and your MainPage are using two different instances of ViewModel. When you create your static resource
<vm:MainPageViewModel x:Name="ViewModel" />
You're creating an instance of MainPageViewModel. This means that when you set the value in from your MainPage instance of MainPageViewModel, it's not propagating to the second instance of MainPageViewModel that your UserControl created.
No worries though, we can fix this.
The DataContext of your user control should be set automatically to its parent's DataContext.
So Let's assume for a moment that you use your UserControl in your MainPage.xaml
<Page.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<MyUserControls:MyUserControl />
In this instance, MyUsercontrol is using the MainPageViewModel as it's DataContext. HOWEVER, this probably won't work, it's not a finished solution.
What you need to do is go to your UserControl's Xaml.CS and create a dependency property that you can bind to.
public class MyUserControl : Control{
public string MyControlsText
{
get { return (string)GetValue(MyControlsTextProperty); }
set { SetValue(MyControlsTextProperty, value); }
}
public static readonly DependencyProperty MyControlsTextProperty =
DependencyProperty.Register("MyControlsText", typeof(string),
typeof(MyUserControl), new PropertyMetadata(String.Empty));
}
Now that we have our dependency property, in our UserControl's Xaml we can bind to it.
<Grid>
<TextBlock x:Name="UserControlTextBlock"
Text="{Binding MyControlsText}" />
</Grid>
And finally in our MainPage.Xaml we can finalize the binding
<MyUserControls:MyUserControl MyControlsText="{Binding TextToShow}" />
So with the above code here is what we are accomplishing:
The DataContext is set in the MainPage.xaml and all of the children of MainPage.xaml share this DataContext
We created a dependency property in our UserControl that we can bind to
We bind the XAML of our UserControl to our dependency property
We Bind our view model's text property to our new user controls dependency property.
I want to realize a binding between several properties. Is it possible ?
I have a main window class named "MainWindow" which owns a property "InputText". This class contains a user control named MyUserControl. MyUserControl has a text box bound to a dependency property "MyTextProperty"
I would like to bind the property "InputText" of my main window with the dependency property "MyTextProperty" of my user control. So, if the user writes a text, I want that the properties "InputText", "MyTextProperty", "MyText" are updated.
User control code:
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MyUserControl.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyUserControl), new PropertyMetadata(0));
public MyUserControl()
{
this.DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
}
WPF user control code:
<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="25 " d:DesignWidth="100"
Background="Black">
<Grid>
<TextBox Height="20" Width="100" Text="{Binding MyText}"></TextBox>
</Grid>
</UserControl>
Main window code:
using System;
using System.Linq;
using System.Windows;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string inputText;
public string InputText
{
get { return inputText; }
set
{
inputText = value;
NotifyPropertyChanged("InputText");
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
}
WPF main window code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:myNS="clr-namespace:WpfApplication1"
Title="MainWindow" Height="80" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<myNS:MyUserControl x:Name="test" MyText="{Binding InputText}"></myNS:MyUserControl>
<Button Name="cmdValidation" Content="Validation" Height="20"></Button>
</StackPanel>
</Grid>
</Window>
Thank you !
If you want your posted code to work with as little changes as possible then:
In MainWindow.xaml, change
MyText="{Binding InputText}"
to
MyText="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.InputText, Mode=TwoWay}"
You need TwoWay if you want the UC to update InputText.
Also, in MyUserControl.xaml.cs, in your DependencyProperty.Register statement, you have the PropertyMetadata default value set to 0 for a string -- change it to something appropriate for a string - like null or string.empty.
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyUserControl), new PropertyMetadata(null));
If you want to change the code up a bit, you could make this more complex in the user control but simpler when you use it by:
Making the dependency property, MyText, bind two way by default
Stop setting the DataContext in the user control
Change the UC xaml text binding to use a relative source to the UC
I always find code easier to understand, so here are modified versions of your files:
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"
xmlns:myNS="clr-namespace:WpfApplication1"
Title="MainWindow" Height="180" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock>
<Run Text="MainWindow.InputText: " />
<Run Text="{Binding InputText}" />
</TextBlock>
<TextBlock>
<Run Text="MyUserControl.MyText: " />
<Run Text="{Binding ElementName=test, Path=MyText}" />
</TextBlock>
<myNS:MyUserControl x:Name="test" MyText="{Binding InputText}"></myNS:MyUserControl>
<Button Name="cmdValidation" Content="Validation" Height="20"></Button>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string inputText = "Initial Value";
public string InputText
{
get { return inputText; }
set
{
inputText = value;
NotifyPropertyChanged("InputText");
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String property)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
}
}
}
}
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="25 " d:DesignWidth="100"
Background="Black">
<Grid>
<TextBox Height="20" Width="100" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=MyText, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</UserControl>
MyUserControl.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MyUserControl.xaml
/// </summary>
public partial class MyUserControl : UserControl
{
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(MyUserControl), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });
public MyUserControl()
{
InitializeComponent();
}
}
}
Firstly,
this.DataContext = this;
No. Just, no. You are overriding the DataContext of the UserControl set by it's parent window.
For your UserControl, give it an x:Name, and bind directly to the dependency property.
<UserControl
...
x:Name="usr">
<TextBox Text="{Binding MyText, ElementName=usr}" ... />
After you've done that, you can then simply bind your MyText property to the DataContext of the MainWindow.
<myNS:MyUserControl x:Name="test" MyText="{Binding InputText}" />
I am newbie in XAML Databinding and I am stuck in this situation. I am using Mahapps MetroWindow.
Suppose that I have a UserControl named usrctrl_Camera_Control. I have a simple button there. The C# code is given below.
namespace TA141501005
{
public partial class usrctrl_Camera_Control : UserControl
{
public usrctrl_Camera_Control()
{
this.DataContext = this;
InitializeComponent();
}
}
The XAML is given below
<UserControl
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:Custom="http://metro.mahapps.com/winfx/xaml/controls" xmlns:local="clr-namespace:TA141501005" x:Class="TA141501005.usrctrl_Camera_Control"
mc:Ignorable="d"
d:DesignHeight="768" d:DesignWidth="1366" Background="#FF2B2B2B" Height="738" Width="1336">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Button x:Name="btn_Test" Content="Button" Grid.Column="1" HorizontalAlignment="Left" Margin="472,59,0,0" VerticalAlignment="Top" Width="75" Grid.RowSpan="2" IsEnabled="{Binding Path=IsNyetEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
</Grid>
The C# code for MainWindow is given below.
namespace TA141501005
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow
{
usrctrl_Camera_Control usrctrl_camera_control;
public bool IsNyetEnabled { get; set; }
public MainWindow()
{
IsNyetEnabled = false;
this.DataContext = this;
InitializeComponent();
usrctrl_camera_control = new usrctrl_Camera_Control();
}
}
}
and the XAML code for MainWindow is given below.
<Controls:MetroWindow x:Class="TA141501005.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="Macromium System Control V1.0ES - TA141501005 [Engineering Sample]" Height="768" Width="1366" Background="#FF2B2B2B" ScrollViewer.VerticalScrollBarVisibility="Disabled" ResizeMode="NoResize" WindowStyle="ThreeDBorderWindow" WindowStartupLocation="CenterScreen" IsMinButtonEnabled="False" IsWindowDraggable="False" ShowMaxRestoreButton="False" ShowMinButton="False" ShowSystemMenuOnRightClick="False" IconOverlayBehavior="Flyouts">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Icons.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button x:Name="btn_test" Content="Button" HorizontalAlignment="Left" Margin="589,590,0,0" VerticalAlignment="Top" Width="75" IsEnabled="{Binding IsNyetEnabled}"/>
</Grid>
I want to binding the property IsEnabled in btn_test in usrctrl_Camera_Control and btn_test in MainWindow into IsNyetEnabled in MainWindow. I set the IsNyetEnabled into false before I do InitializeComponent() in MainWindow.
The binding between btn_test.IsEnabled in MainWindow into IsNyetEnabled in MainWindow is done flawlessly. The btn_test in MainWindow is no longer enabled. (I know, I need to implement INotifyPropertyChanged to notify the subsriber if there is any change but for now, just leave it as is for simplicity).
But, the binding between btn_test.IsEnabled in usrctrl_Camera_Control into IsNyetEnabled in MainWindow is failed. I have used Visual Studio "Create Data Binding" wizard but it always return error when compiling.
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='TA141501005.MainWindow', AncestorLevel='1''. BindingExpression:Path=IsNyetEnabled; DataItem=null; target element is 'Button' (Name='btn_Test'); target property is 'IsEnabled' (type 'Boolean')
Do you have any suggestion? I have tried for whole day without a luck.
Is there any way to access parent datacontext without removing this.Datacontext = this?
Looking forward for your suggestion and explanation.
Thank you very much.
Edit.
I display my UserControl via Flyout.
Window parentWindow = Window.GetWindow(this);
object obj = parentWindow.FindName("mainFlyout");
Flyout flyout = (Flyout) obj;
flyout.Content = new SomeFlyOutUserControl();
flyout.IsOpen = !flyout.IsOpen;
Create the IsNyetEnabled property as a dependency property on the MainForm. This is the mechanism that will propagate changes in WPF
Example:
public static readonly DependencyProperty IsNyetEnabledProperty =
DependencyProperty.Register("IsNyetEnabled", typeof(bool),
typeof(MainWindow),new FrameworkPropertyMetadata(false));
public bool IsNyetEnabled
{
get { return (bool)GetValue(MainWindow.IsNyetEnabled); }
set { SetValue(MainWindow.IsNyetEnabled, value); }
}
To that you can bind your Button in the MainWindow directly.
For the UserControl though we have to add a shim, because the UserControl won't be able to find the ancestor when you create it.
Create on your UserControl like Moez posted another DependencyProperty:
public static readonly DependencyProperty IsButtonEnabledProperty =
DependencyProperty.Register("IsButtonEnabled", typeof(bool),
typeof(usrctrl_Camera_Control),new FrameworkPropertyMetadata(false));
public bool IsButtonEnabled
{
get { return (bool)GetValue(usrctrl_Camera_Control.IsButtonEnabledProperty); }
set { SetValue(usrctrl_Camera_Control.IsButtonEnabledProperty, value); }
}
Bind your UserControl button IsEnabled property to that.
Now as a last step we will have to connect the two Dependency Properties when you create your UserControl.
<local:usrctrl_Camera_Control x:Name="yourControl" ...Whatever else... IsButtonEnabled="{Binding Path=IsNyetEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}} />
This should work now.
I suggest you use DependencyProperty :
public static readonly DependencyProperty IsButtonEnabledProperty = DependencyProperty.Register(
"IsButtonEnabled", typeof(bool), typeof(UserControl1), new PropertyMetadata(default(bool)));
public bool IsButtonEnabled
{
get { return (bool)GetValue(IsButtonEnabledProperty); }
set { SetValue(IsButtonEnabledProperty, value); }
}
// Here Rename your UserControl To Test
<Grid>
<Button x:Name="btn_Test" Content="Button" Grid.Column="1" HorizontalAlignment="Left" Margin="472,59,0,0" VerticalAlignment="Top" Width="75" Grid.RowSpan="2" IsEnabled="{Binding Path=IsButtonEnabled, ElementName=Test}"}" />
</Grid>
As #Frank J suggested, I modify his last step.
I create the usercontrol during runtime, not from the XAML. Therefore it would be look like this for the last step.
usrctrl_camera_control = new usrctrl_Camera_Control();
Binding b = new Binding("IsNyetEnabled");
b.Source = this;
b.Mode = BindingMode.OneWay;
usrctrl_camera_control.SetBinding(usrctrl_Camera_Control.IsButtonEnabledProperty, b);
Thanks all.. I hope it would be useful for those who have the same problem with me.