My English skill is poor because I'm not a native English speaker.
I hope you to understand.
I need to use the WindowsFormsHost control because using the DataGridView of the WinForm.
I can't DataGrid control in WPF because of some reasons.
In the .cs file, I succeeded using the WindowsFormsHost with the DataGridView of the WinForm.
The code is as shown below.
var tabItem = new ClosableTab();
#region Core Logic
var winformControl = new WindowsFormsHost();
winformControl.VerticalAlignment = VerticalAlignment.Stretch;
winformControl.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
winformControl.Child = new DataGridView();
DataGridView parsingTableView = winformControl.Child as DataGridView;
parsingTableView.EditMode = DataGridViewEditMode.EditProgrammatically;
parsingTableView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
parsingTableView.DataSource = mainWindow.editor.TokenTable;
parsingTableView.CellMouseEnter += new DataGridViewCellEventHandler(this.tableGridView_CellMouseEnter);
tabItem.Content = winformControl;
#endregion
this.mainWindow.tablControl.Items.Add(tabItem);
Now I want to convert the above logic to the XAML so I wrote the logic as shown below.
First, I defined DataTemplate to display the DataTable type.
// In resource file
xmlns:systemData="clr-namespace:System.Data;assembly=System.Data"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
<DataTemplate DataType="{x:Type systemData:DataTable}">
<WindowsFormsHost>
<WindowsFormsHost.Child>
<wf:DataGridView DataSource="{Binding}" EditMode="EditProgrammatically" AutoSizeColumnsMode="AllCells"/>
</WindowsFormsHost.Child>
</WindowsFormsHost>
</DataTemplate>
Now the above DataTemplate is used when binding to Content as shown the below code.
// In xaml file
<Grid>
<TabControl ItemsSource="{Binding SelectedItem}">
<TabItem Header="{lex:Loc Key=TokenTable}" Content="{Binding TokenTable}"/>
</TabControl>
</Grid>
When I execute the above code, I faced an error as below at the DataSource="{Binding}".
If I have to translate the above error using my poor English skill.
The error tells me. "It can't configure 'Binding' on the 'DataSource' property."
"The 'Binding' can configure only on the DependencyProperty of the DependencyObject."
I think I know what error trying to say to me but I don't know what I do to solve the above problem.
What should I do to solve this problem?
Thank you for reading.
You can't bind directly to the DataSource property of a DataGridView because it's not a dependency property.
What you can do is to work around this by create an attached property that you set on the WindowsFormsHost as suggested here, and then set the property of the DataGridView using the callback of the attached property:
public static class WindowsFormsHostMap
{
public static readonly DependencyProperty DataSourceProperty
= DependencyProperty.RegisterAttached("DataSource", typeof(object),
typeof(WindowsFormsHostMap), new PropertyMetadata(OnPropertyChanged));
public static string GetText(WindowsFormsHost element) => (string)element.GetValue(DataSourceProperty);
public static void SetText(WindowsFormsHost element, object value) => element.SetValue(DataSourceProperty, value);
static void OnPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var dataGridView = (sender as WindowsFormsHost).Child as DataGridView;
dataGridView.DataSource = e.NewValue;
}
}
XAML:
<WindowsFormsHost local:WindowsFormsHostMap.DataSource="{Binding}">
Related
I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.
XAML
<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">
<ComboBox x:Name="comboBox" Width="135"
SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>
Code Behind
The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.
private FontFamily _SelectedFontFamily;
public FontFamily SelectedFontFamily
{
get
{
return _SelectedFontFamily;
}
set
{
if (_SelectedFontFamily != value)
{
_SelectedFontFamily = value;
// Modify External Dependency Property Value.
if (value != SelectedFont.FontFamily)
{
SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
}
// Notify.
RaisePropertyChanged(nameof(SelectedFontFamily));
}
}
}
The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.
public Typeface SelectedFont
{
get { return (Typeface)GetValue(SelectedFontProperty); }
set { SetValue(SelectedFontProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedFont. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedFontProperty =
DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedFontPropertyChanged)));
private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as FontSelector;
var newFont = e.NewValue as Typeface;
if (newFont != null)
{
instance.SelectedFontFamily = newFont.FontFamily;
}
}
EDIT
I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.
<CollectionViewSource x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.
I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)
Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:
public string SelectedFontFamily
{
get { return (string)GetValue(SelectedFontFamilyProperty); }
set { SetValue(SelectedFontFamilyProperty, value); }
}
public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));
The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:
<Window
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">
- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):
public string FontFamily {get; set;}
- in the Window's xaml you would use the UC this way:
<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.
Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.
Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.
Here is an existing answer I have now Found.
Thanks
My objective is to include the Label's text in an error message if the content of the Label's TextBox is not valid. During validation, when only the TextBox object is easily obtained, I would like to obtain the reference to the Label object which has had its Target property bound to that TextBox.
In other words, given the source of a binding, I would like to return or retrieve the target of that binding. The WPF BindingOperations.GetBindingExpression() and related methods require that the target object be known already.
In WPF XAML I have this:
<Label Target="{Binding ElementName=RatingTextBox}">_Rating:</Label>
<TextBox Name ="RatingTextBox"/>
In C# code-behind I tried this:
BindingExpression be = RatingTextBox.GetBindingExpression(TextBox.TextProperty);
string format = be.ParentBinding.StringFormat;
However, be.ParentBinding above is null even though my TextBox is definitely bound by the label because the hot key "[Alt]-R" works. Can my TextBox get that Label's text somehow from the C# code-behind?
If I understand correctly, you are looking for a way to automatically bind the Tooltip property of your TextBox to the Content property of whatever Label object the TextBox is a target of.
Unfortunately, to do this most easily would require a mechanism in WPF to, given the source of a binding, identify its target (or targets…a single source can be bound to multiple targets, of course). And as far as I know, no such mechanism exists as such.
However, I can think of at least a couple of different alternatives that should accomplish a similar effect:
When initializing the window, enumerate all the Label objects to find their targets, and update the targets' Tooltip properties accordingly. Either just set them explicitly, or bind the properties to the Label.Content property.
Reverse the direction the Label target is declared. I.e. create an attached property that can be used on the TextBox object, indicating which Label should target it. Then use this attached property to initialize the Tooltip property appropriate (e.g. in the attached property code, bind or set the Tooltip property, or have some other property that is also bound to the attached property and when it changes, handle the binding or setting there).
The motivation for using an attached property in the second option is to allow the label/target relationship to still be declared just once in the XAML (i.e. avoiding redundancy). It's just that the declaration occurs in the target object (i.e. the TextBox) instead of the label object.
Here are a couple of examples showing what I mean…
First option above:
XAML:
<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label x:Name="label1" Content="_Label:" Target="{Binding ElementName=textBox1}"/>
<TextBox x:Name="textBox1"/>
</StackPanel>
</StackPanel>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitTooltips(this);
}
private void InitTooltips(FrameworkElement element)
{
foreach (FrameworkElement child in
LogicalTreeHelper.GetChildren(element).OfType<FrameworkElement>())
{
Label label = child as Label;
if (label != null)
{
BindingExpression bindingExpression =
BindingOperations.GetBindingExpression(label, Label.TargetProperty);
if (bindingExpression != null)
{
TextBox textBox =
FindName(bindingExpression.ParentBinding.ElementName) as TextBox;
if (textBox != null)
{
// You could just set the value, as here:
//textBox.ToolTip = label.Content;
// Or actually bind the value, as here:
Binding binding = new Binding();
binding.Source = label;
binding.Path = new PropertyPath("Content");
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
textBox, TextBox.ToolTipProperty, binding);
}
}
}
InitTooltips(child);
}
}
}
Second option above:
XAML:
<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel Orientation="Horizontal">
<!-- Note that the Target property is _not_ bound in the Label element -->
<Label x:Name="label1" Content="_Label:"/>
<!-- Instead, it's specified here via the attached property: -->
<TextBox x:Name="textBox1" l:TooltipHelper.TargetOf="{Binding ElementName=label1}"/>
</StackPanel>
</StackPanel>
</Window>
C#:
static class TooltipHelper
{
public static readonly DependencyProperty TargetOfProperty =
DependencyProperty.RegisterAttached("TargetOf", typeof(Label),
typeof(TooltipHelper), new PropertyMetadata(null, _OnTargetOfChanged));
public static void SetTargetOf(FrameworkElement target, Label labelElement)
{
target.SetValue(TargetOfProperty, labelElement);
}
public static Label GetTargetof(FrameworkElement target)
{
return (Label)target.GetValue(TargetOfProperty);
}
private static void _OnTargetOfChanged(
DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Label oldLabel = (Label)e.OldValue,
newLabel = (Label)e.NewValue;
if (oldLabel != null)
{
BindingOperations.ClearBinding(oldLabel, Label.TargetProperty);
BindingOperations.ClearBinding(target, FrameworkElement.ToolTipProperty);
}
if (newLabel != null)
{
Binding binding = new Binding();
binding.Source = newLabel;
binding.Path = new PropertyPath("Content");
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
target, FrameworkElement.ToolTipProperty, binding);
binding = new Binding();
binding.Source = target;
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
newLabel, Label.TargetProperty, binding);
}
}
}
Note that in the second option, no new code is required in the window class. Its constructor can just call InitializeComponent() as usual and that's it. All of the code-behind winds up in the TooltipHelper class, which is referenced in the XAML itself.
Requirement
Let's start with what I am trying to achieve. I want to have a grid with 2 columns and a grid splitter (there is a little more to it that that, but let's keep it simple). I want to be able to use this grid in a lot of different places, so instead of creating it each time I want to make a custom control that contain two ContentPresenters.
The end goal is effectively to be able to write XAML like this:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
IMPORTANT: Notice that I want to apply a Name to my Label element.
Attempt 1
I did a lot of searching for solutions, and the best way I found was to create a UserControl along with a XAML file that defined my grid. This XAML file contained the 2 ContentPresenter elements, and with the magic of binding I was able to get something working which was great. However, the problem with that approach is not being able to Name the nested controls, which results in the following build error:
Cannot set Name attribute value 'MyName' on element 'MyGrid'. 'MyGrid'
is under the scope of element 'MyControls', which already had a name
registered when it was defined in another scope.
With that error in hand, I went back to Dr. Google...
Attempt 2 (current)
After a lot more searching I found some information here on SO that suggested the problem was due to having an associated XAML file with the MyGrid class, and the problem should be solvable by removing the XAML and creating all the controls via code in the OnInitialized method.
So I headed off down that path and got it all coded and compiling. The good news is that I can now add a Name to my nested Label control, the bad news is nothing renders! Not in design mode, and not when running the application. No errors are thrown either.
So, my question is: What am I missing? What am I doing wrong?
I am also open to suggestions for other ways to meet my requirements.
Current code
public class MyGrid : UserControl
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
Grid MainGrid;
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//Create control elements
MainGrid = new Grid();
//add column definitions
ColumnDefinition leftColumn = new ColumnDefinition()
{
Name = "LeftColumn",
Width = new GridLength(300)
};
MainGrid.ColumnDefinitions.Add(leftColumn);
MainGrid.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = GridLength.Auto
});
//add grids and splitter
Grid leftGrid = new Grid();
Grid.SetColumn(leftGrid, 0);
MainGrid.Children.Add(leftGrid);
GridSplitter splitter = new GridSplitter()
{
Name = "Splitter",
Width = 5,
BorderBrush = new SolidColorBrush(Color.FromArgb(255, 170, 170, 170)),
BorderThickness = new Thickness(1, 0, 1, 0)
};
MainGrid.Children.Add(splitter);
Grid rightGrid = new Grid();
Grid.SetColumn(rightGrid, 1);
MainGrid.Children.Add(rightGrid);
//add content presenters
ContentPresenter leftContent = new ContentPresenter();
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
leftGrid.Children.Add(leftContent);
ContentPresenter rightContent = new ContentPresenter();
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
rightGrid.Children.Add(rightContent);
//Set this content of this user control
this.Content = MainGrid;
}
}
After some discussion via comments, it quickly became clear that neither of my attempted solutions was the correct way to go about it. So I set out on a third adventure hoping this one would be the final solution... and it seems it is!
Disclaimer: I do not yet have enough experience with WPF to confidently say that my solution is the best and/or recommended way to do this, only that it definitely works.
First of all create a new custom control: "Add" > "New Item" > "Custom Control (WPF)". This will create a new class that inherits from Control.
In here we put our dependency properties for bind to out content presenters:
public class MyGrid : Control
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
}
When you add this class file in Visual Studio, it will automatically create a new "Generic.xaml" file in the project containing a Style for this control, along with a Control Template within that style - this is where we define our control elements...
<Style TargetType="{x:Type MyControls:MyGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControls:MyGrid}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<ContentPresenter x:Name="LeftContent" />
</Grid>
<GridSplitter Width="5" BorderBrush="#FFAAAAAA" BorderThickness="1,0,1,0">
</GridSplitter>
<Grid Grid.Column="1">
<ContentPresenter x:Name="RightContent" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The final step is to hook up the bindings for the 2 content presenters, so back to the class file.
Add the following override method to the MyGrid class:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Apply bindings and events
ContentPresenter leftContent = GetTemplateChild("LeftContent") as ContentPresenter;
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
ContentPresenter rightContent = GetTemplateChild("RightContent") as ContentPresenter;
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
}
And that's it! The control can now be used in other XAML code like so:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
Thanks to #NovitchiS for your input, your suggestions were vital in getting this approach to work
My program contains several instances of this line:
<local:MyClass Data="{Binding}"/>
I.e. the property Data is bound to the data context of the surrounding window. When the value of the window's DataContext changes, the binding is sometimes updated, sometimes not; it depends on the position of <local:MyClass...> in the XAML file.
Here is an example (EDIT: I changed the {Binding} to {Binding Path=DataContext, ElementName=myWindow} to emphasise that the problem is not related to inheritance of DataContext):
XAML code:
<Window x:Class="BindTest.MainWindow"
x:Name="myWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindTest"
Title="Binding Test" Height="101" Width="328">
<Window.Tag>
<local:MyClass x:Name="bindfails" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</Window.Tag>
<StackPanel Orientation="Horizontal">
<Button Margin="5" Padding="5" Click="SetButtonClicked">Set DataContext</Button>
<Button Margin="5" Padding="5" Click="ReadButtonClicked">Read Bound Property</Button>
<local:MyClass x:Name="bindworks" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</StackPanel>
</Window>
C# code:
using System.Windows;
namespace BindTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
}
private void ReadButtonClicked(object sender, RoutedEventArgs e)
{
string txtA = (bindfails.Data == null ? "null" : bindfails.Data.ToString());
string txtB = (bindworks.Data == null ? "null" : bindworks.Data.ToString());
MessageBox.Show(string.Format("bindfails.Data={0}\r\nbindworks.Data={1}", txtA, txtB));
}
}
public class MyClass : FrameworkElement
{
#region Dependency Property "Data"
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(MyClass), new UIPropertyMetadata(null));
#endregion
}
}
First press button "Set DataContext" to change the data context. Then press button "Read Bound Property", which display this message:
bindfails.Data=null
bindworks.Data=1234
Obviously, the data binding was only updated for the MyClass element that is child of the StackPanel; but the data binding was not updated for the MyClass element that is referenced by Window.Tag.
EDIT2: I also have found out that binding works when adding the binding programmatically inside MainWindow's constructor:
Binding binding = new Binding("DataContext") {Source = this};
bindfails.SetBinding(MyClass.DataProperty, binding);
The binding only fails when it is declared in XAML. Furthermore, the problem is not specific to DataContext; it also happens when I use other Window properties, such as Title.
Can anyone explain this behavior and suggest how to allow the use of {Binding} in XAML in both cases?
EDIT3: The above code is not entirely equivalent to the {Binding} markup extension. The 100% equivalent code is:
Binding binding = new Binding("DataContext") {ElementName = "myWindow"};
bindfails.SetBinding(MyClass.DataProperty, binding);
When I use that code, binding also fails (like when binding in XAML) and the following diagnostics message is written to debug output:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'.
Obviously, the ElementName property only searches up the visual tree or logical tree, even though this is not documented in the WPF online documentation. Probably, there is no easy way to set such a binding in XAML.
The dataContext is passed only throught the Object Tree. The Property tag is not in the visual tree and will not respond to DataContext changed Event and the binding is not refreshed without this event.
See :
Dependency property identifier field: DataContextProperty from the FrameworlElement
Data context is a concept that allows objects to inherit binding-specifying information from their parents in the object tree
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontext(v=vs.95).aspx
I suppose it does not work because when you write {Binding}, the context of the data to be bound is inherited from the parent. In case of bindworks, it's a StackPanel, which inherits DataContext from Window, however bindfails.Parent property is null.
I wonder why have you put a control in Window's tag element. If you must keep it declared in Tag node for some reason, you can update its DataContext directly, so just change SetButtonClicked method to:
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
bindfails.DataContext = DataContext;
}
Another simple method to make it work is just taking bindfails out of Window.Tag and placing it somewhere in the Window, i.e. in the StackPanel.
Next, in the Window's constructor, write this.Tag = bindfails. If you don't want the TextBox to appear in the form, you can set its Visibility to Collapsed (or put it inside a collapsed container control).
I'm having yet another WPF binding issue. Just when I think I've got this stuff figured out, I run into more problems... :S
Anyway... I've created a custom user control for selecting files. It's a simple textbox followed by a button contained within a grid. The property of the control with which I am working is called FilePath and the TextBox on this control is bound to that property. When the button is clicked, a SaveFileDialog is opened and the user selects a file. The UI correctly updates after the user selects the file.
The problem I seem to be having is that when I bind an object to the control (in this instance I have an object with a DocumentFilePath property) the object doesn't update when a new file is selected.
Here's the relevant code within my user control:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FileSave), new UIPropertyMetadata(string.Empty, OnFilePathChanged));
public string FilePath
{
get
{
return this.GetValue(FilePathProperty) as string;
}
set
{
this.SetValue(FilePathProperty, value);
this.OnPropertyChanged("FilePath");
}
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
private static void OnFilePathChanged(object sender, DependencyPropertyChangedEventArgs e)
{
((FileSave)sender).OnPropertyChanged("FilePath");
}
And the user control is added into my Window programatically by using reflection on my object:
private void AddFileSave(PropertyInfo pi)
{
FileSave fs = new FileSave();
Binding b = new Binding(pi.Name);
fs.SetBinding(FileSave.FilePathProperty, b);
this.AddToGrid(fs); //adds the control into my window's grid in the correct row and column; nothing fancy here
}
It may be worth noting that if I load the window with an existing object, my user control displays properly but still won't register any changes within the object to which it is bound.
Please let me know if you guys need any more info.
Thanks in advance,
Sonny
EDIT: I've found a way around the problem, but this probably isn't a good solution. By watching the debugger carefully I found that when I set the FilePath property within my control, the object was being unbound. If anyone can shed some light on that, I would be most appreciative. In the mean time, I've changed the code that opens my SaveFileDialog to look like this:
private void Button_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
ofd.Multiselect = false;
ofd.Title = "Select document to import...";
ofd.ValidateNames = true;
ofd.ShowDialog();
if (this.GetBindingExpression(FilePathProperty) == null)
{
this.FilePath = ofd.FileName;
}
else //set value on bound object (THIS IS THE NEW PORTION I JUST ADDED)
{
BindingExpression be = this.GetBindingExpression(FilePathProperty);
string propName = be.ParentBinding.Path.Path;
object entity = be.DataItem;
System.Reflection.PropertyInfo pi = entity.GetType().GetProperty(propName);
pi.SetValue(entity, ofd.FileName, null);
}
if (!string.IsNullOrWhiteSpace(this.FilePath))
{
_fileContents = new MemoryStream();
using (StreamReader sr = new StreamReader(this.FilePath))
{
_fileContents = new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(sr.ReadToEnd()));
}
}
else
{
_fileContents = null;
}
}
You're not specifying anywhere in your code that the FilePath property should be TwoWay so updates of the DP value won't get pushed to the bound source object's property. You can use either:
Binding b = new Binding(pi.Name){ Mode = BindingMode.TwoWay };
or you can set up your Dependency Property to use a default of TwoWay:
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register(
"FilePath", typeof(string), typeof(FileSave),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnFilePathChanged));
You should also follow Robert's suggestion of removing the manual PropertyChange event, and also don't EVER add any code other than GetValue and SetValue in your DP wrapper property. XAML calls GetValue and SetValue directly so will skip over anything else you add there - which can lead to very nasty bugs.
Why, yes! I most certainly can shed some light on that!
Also, if you're using .Net 4.0, today's your lucky day!
Consider the following fine method on your DependencyObject:
SetCurrentValue();
Yes! With this SINGULAR method, all your woes will drift away as a bad dream at the rooster's crow! (Well, ok, not really, but that is the method you're looking for.)
Short story very short: When you programmatically SetValue() on a control in your view layer, you blow away your bindings. SetCurrentValue() was added to the framework because you frequently want to drive a change in your bound object by setting that value directly. An alternate design would be to set the value in your bound object programmatically and let the updated value get pulled back into the view, but that's frequently clumsy.
(I strongly suspect that the absence of this method up to this point is largely responsible for the utter failure of the vast majority of NumericUpDown controls in WPF.)
First, you don't need to raise the PropertyChanged event when a dependency property changes; with dependency properties, change notification comes for free.
What's probably happening here: The default behavior for UpdateSourceTrigger is LostFocus, i.e. the source gets updated when the user presses TAB to move to the next field, or clicks on another control, or whatever. The text box isn't losing focus after your SaveFileDialog sets Text (since it probably doesn't even have the focus in the first place), so the source update never gets triggered.
To make it update the source whenever the Text property changes, set the UpdateSourceTrigger to PropertyChanged.
If that doesn't work, watch the Output window for binding errors.
Edit:
Here's a little prototype application I built. It works just fine: typing in the text box sets the property, clicking on the "Save" button sets the property, and the binding in the main window gets updated properly no matter what.
<Window x:Class="DependencyPropertyBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:demo="clr-namespace:DependencyPropertyBindingDemo"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<demo:FilePicker x:Name="Picker"
DockPanel.Dock="Top"
Margin="5" />
<TextBox DockPanel.Dock="Top"
Text="{Binding ElementName=Picker, Path=FilePath}" />
<TextBlock />
</DockPanel>
</Window>
<UserControl x:Class="DependencyPropertyBindingDemo.FilePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<TextBox DockPanel.Dock="Left"
Width="200"
Text="{Binding FilePath, UpdateSourceTrigger=PropertyChanged}" />
<Button Width="50"
DockPanel.Dock="Left"
Command="{Binding Path=SaveCommand}">Save</Button>
<TextBlock />
</DockPanel>
</UserControl>
public partial class FilePicker : UserControl
{
public FilePicker()
{
SaveCommand = new FilePickerSaveCommand(this);
DataContext = this;
InitializeComponent();
}
public ICommand SaveCommand { get; set; }
public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FilePicker));
public string FilePath
{
get
{
return GetValue(FilePathProperty) as string;
}
set
{
SetValue(FilePathProperty, value);
}
}
}
public class FilePickerSaveCommand : ICommand
{
private FilePicker _FilePicker;
public FilePickerSaveCommand(FilePicker picker)
{
_FilePicker = picker;
}
public void Execute(object parameter)
{
_FilePicker.FilePath = "Testing";
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}