I want to bind some properties (FooClass.FooString) of a custom class (FooClass) to my MainWindow. Now below (Working known behavior) is the default working solution if binding some data to a gui.
What I want to do is in the second code block (Not working, but desired behavior). Expose some properties of another class objectto the gui and update it.
**Problem**: TheTestStringis not getting updated (on the gui, code behind works). ThePropertyChangedeventis alsonull` (not subscribed?!).
Is this the wrong way how to bind data?
If I bind the complete FooClass object to the gui and set Path (of TextBlock) to Foo.FooString, the gui and string is updated. But I don't want to do it this way.
Is this the way how to solve it?
Working known behavior
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
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"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Not working, but desired behavior
public partial class MainWindow : Window
{
public string TestString => _Foo.FooString;
private readonly FooClass _Foo;
public MainWindow()
{
_Foo = new FooClass();
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
_Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
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"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=TestString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Solution 1
Subscribe to the Foo.PropertyChanged event and route it to MainWindow.PropertyChanged.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
Foo.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I might not have fully understood what you want, but here is a working example of data binding, that is somewhat close to your example.
The two main changes were:
Set the datacontext to the VM and not the code behind
Actually give OnPropertyChanged the argument it needs to correctly trigger the refresh, the name of the property.
Result:
MainWindow.xaml
<Window x:Class="ListViewColor.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"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Aqua"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace ListViewColor
{
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
}
FooClass.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class FooClass : INotifyPropertyChanged
{
private string _FooString = "Empty";
public string FooString
{
get
{
return _FooString;
}
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
public void ChangeTheProperty()
{
FooString = "Loaded";
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I hope that helps!
Related
I'm trying to create a usercontrol that can resize and collapse/expand (with an animation). The resizing stop working when I play the collapse/expand animation.
Complete test application can be found here: App
EDIT: here's the relevant code as requested
MyControl.xaml:
<UserControl x:Class="WpfApp1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<Grid Background="#FF935E5E">
<Thumb Width="8"
HorizontalAlignment="Right"
Margin="0,0,-4,0"
DragDelta="Thumb_DragDelta"
Cursor="SizeWE"/>
</Grid>
MyControl.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MyControl.xaml
/// </summary>
public partial class MyControl : UserControl
{
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(MyControl), new PropertyMetadata(true, OnIsOpenChanged));
private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl control = d as MyControl;
control.PlayAnimation();
}
public double OpenWidth
{
get { return (double)GetValue(OpenWidthProperty); }
set { SetValue(OpenWidthProperty, value); }
}
public static readonly DependencyProperty OpenWidthProperty =
DependencyProperty.Register("OpenWidth", typeof(double), typeof(MyControl), new PropertyMetadata(300d, OnOpenWidthChanged));
private static void OnOpenWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyControl control = d as MyControl;
if (control.IsOpen)
control.Width = control.OpenWidth;
}
public MyControl()
{
InitializeComponent();
if (IsOpen)
Width = OpenWidth;
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
OpenWidth += e.HorizontalChange;
}
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
BeginAnimation(WidthProperty, sizeAnimation);
}
}
}
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"
Title="MainWindow" Height="350" Width="700">
<DockPanel>
<local:MyControl IsOpen="{Binding ControlIsOpen}"
OpenWidth="{Binding ControlOpenWidth}"/>
<Grid Background="Green">
<Button Width="100"
Height="20"
Content="Test Animation"
Click="Button_Click"/>
</Grid>
</DockPanel>
MainWindow.xaml.cs:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion INotifyPropertyChanged
private bool _ControlIsOpen = true;
public bool ControlIsOpen
{
get => _ControlIsOpen;
set
{
_ControlIsOpen = value;
OnPropertyChanged();
}
}
private double _ControlOpenWidth = 300d;
public double ControlOpenWidth
{
get => _ControlOpenWidth;
set
{
_ControlOpenWidth = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ControlIsOpen = !ControlIsOpen;
}
}
}
Thanks for the help :)
The animation actually never stopsYou should specifiy FillBehavior to Stop. In this case the annimation will stop updating the property after the final value is reached.
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.FillBehavior = FillBehavior.Stop;
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
sizeAnimation.Completed += OnAnimationCompleted;
BeginAnimation(WidthProperty, sizeAnimation);
}
private void OnAnimationCompleted(object sender, EventArgs e)
{
Width = IsOpen ? OpenWidth : 0;
}
The default value is HoldEnd. And the storyboard will modify the Width untill it is not explicitly stopped.
https://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.fillbehavior(v=vs.110).aspx
Some more info https://learn.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-set-a-property-after-animating-it-with-a-storyboard
Well thanks to Dmitry idea, i've been able to solve it, by setting the fill behavior to stop and forcing to width to be either 0 or the open width:
private void PlayAnimation()
{
DoubleAnimation sizeAnimation = new DoubleAnimation(IsOpen ? OpenWidth : 0, TimeSpan.FromMilliseconds(250));
sizeAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseInOut };
sizeAnimation.FillBehavior = FillBehavior.Stop;
sizeAnimation.Completed += (s, e) => Width = (IsOpen ? OpenWidth : 0);
BeginAnimation(WidthProperty, sizeAnimation);
}
Thanks all :)
I'm very new to WPF. I'm currently doing a code to detect joints coordinate using the Kinect SDK and displaying on a simple textbox in WPF. The code to detect joints are in a private void Window_Loaded(object sender, RoutedEventArgs e) method. To display the coordinates, I used DataContext. Without further ado, let's just see the XAML code:
<Window x:Class="Prototype.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="480" Width="640">
<Grid>
<TextBox x:Name="coordinateText" Width="150" Height="20" Margin="441,409,27,12" Text="{Binding Path=xInfo}"/>
</Grid>
And this is my C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.Kinect.Nui;
using Coding4Fun.Kinect.Wpf;
namespace Prototype
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//this.DataContext = new Coordinate { xInfo = "5" };
}
Runtime nui = new Runtime();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Coordinate { xInfo = "5" };
nui.Initialize(RuntimeOptions.UseSkeletalTracking); //code for detecting joints
//some code for detecting joints
}
public class Coordinate
{
public string xInfo { get; set; }
public string yInfo { get; set; }
public string zInfo { get; set; }
}
}
}
The thing is the information will not be loaded in the textbox if this.DataContext = new Coordinate { xInfo = "5" }; is not placed in the MainWindow. I have to put it in the Window_Loaded method. Any solutions?
As Coder323 said When window is loaded you need tell WPF TextBox that the Value of the variable xInfo is changed so you should use INotifyPropertyChanged in your Model Class
then where ever you change theValue of your Object it will pick up the changed Value... also
Just Set the DataContext=myCordinate in the Window Constructor then, make my cordinate a variable in the window class.
public class Coordinate : INotifyPropertyChanged
{
private string xInfo;
public string XInfo {
get{retun value};
set{
xInfo=Value;
FirePropertyChanged("XInfo")
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Do this for other properties and now you can set the value of myCordinate.XInfo="what ever you like" in any event it will notify to your view that the respective property has changed..
I am putting my complete solution here
My Coordinate class
public class Coordinates : INotifyPropertyChanged
{
private string xInfo;
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public string XInfo
{
get { return xInfo; }
set
{
xInfo = value;
InvokePropertyChanged(new PropertyChangedEventArgs("XInfo"));
}
}
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
#endregion
}
My Xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<TextBox Text="{Binding Path=XInfo}" Height="30" Widht="100"></TextBox>
</Grid>
My Xaml.cs
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Coordinates myCoordinates;
public MainWindow()
{
myCoordinates=new Coordinates();
this.DataContext = myCoordinates;
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
myCoordinates.XInfo = "Acbd";
}
}
}
And yes this test Project i made... is working
This might Help :)
I have a textbox which I need to bind a string to.
<TextBox Name="txtDoc" Margin="5" Text ="{Binding Source={x:Static local:DocumentViewModel.FileText}, Path=FileText}">
The FileText property is changed on a different class:
DocumentViewModel.GetInstance().FileText = File.ReadAllText(document.Path);
The DocumentViewModel is a class with Singleton:
public class DocumentViewModel : INotifyPropertyChanged
{
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
fileText = value; // Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("FileText");
}
}
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private static DocumentViewModel instance = new DocumentViewModel();
private DocumentViewModel() { }
public static DocumentViewModel GetInstance()
{
return instance;
}
}
I need to be able to change the value of the FileText property and reflect this change in the textbox.
It's not working.
I tried using TextBox as a static property but then the Onp
Try to set the source to your viewmodel instead of the property itself, and set the instance property to public? {Binding Source={x:Static local:DocumentViewModel.instance}, Path=FileText}
Edit: Included a complete example, that working for me:
Xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
<TextBox Name="txtDoc" Margin="5"
Text="{Binding Source={x:Static local:DocumentViewModel.Instance}, Path=FileText}" />
</Window>
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DocumentViewModel.Instance.FileText = "Hello world!";
}
}
public class DocumentViewModel : INotifyPropertyChanged
{
#region Singleton implementation
// Static constructor to create the singleton instance.
static DocumentViewModel()
{
DocumentViewModel.Instance = new DocumentViewModel();
}
public static DocumentViewModel Instance { get; private set; }
#endregion
private static string fileText;
public string FileText
{
get { return fileText; }
set
{
if (fileText != value)
{
fileText = value;
OnPropertyChanged("FileText");
}
}
}
#region INotifyPropertyChanged
private void OnPropertyChanged(string filetext)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(filetext));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
now I'm doing so Binding:
field:
private readonly RestaurantContext m_context = new RestaurantContext();
init:
m_context.Load(m_context.GetGroupQuery());
this.dataGridGroup.DataContext = m_context.Groups;
How do this in xaml ?
Juste expose your m_context, ensure that the class that encapsulate this property is set as the datacontext of your view and bind your dataGridGroup datacontext to your prperty.
For example :
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new WindowViewModel();//this will set the WindowViewModel object below as the datacontext of the window
}
}
public class WindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
public WindowViewModel()
{
restContext = new RestaurantContext();//init 1
restContext.Load(restContext.GetGroupQuery());//init 2
InvokePropertyChanged(new PropertyChangedEventArgs("RestContext"));//notify the view th update datacontext
}
private RestaurantContext restContext;
/// <summary>
/// Gets or sets the RestContext (which will be vound to the datagrid datacontext)
/// </summary>
public RestaurantContext RestContext
{
get { return restContext; }
set
{
if (RestContext != value)
{
restContext = value;
InvokePropertyChanged(new PropertyChangedEventArgs("RestContext"));
}
}
}
}
/// <summary>
/// Whatever class
/// </summary>
public class RestaurantContext
{
public void Load(object getGroupQuery)
{
//Whatever here
}
public object GetGroupQuery()
{
//Whatever here
return new object();
}
IEnumerable Groups { get; set; }
}
XAML :
<Window x:Class="StackOverflow.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Width="100" Height="100" >
<Grid>
<DataGrid DataContex="{Binding RestContext.Groups}"></DataGrid>
</Grid>
</Window>
In your XAML:
<DataGrid x:Name="dataGridGroup" DataContext={Binding Groups} />
It will automatically bind to the Groups property of your ViewModel
I'm trying to bind a value down from a Window into a UserControl inside a UserControl. But, for some reason, the inner UserControl never even attempts to bind as far as I can tell.
MainWindow.xaml
<Window x:Class="PdfExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:PdfExample">
<Grid>
<my:FileSystemBrowser HorizontalAlignment="Left" x:Name="fileSystemBrowser1" VerticalAlignment="Top" Height="311" Width="417" RootPath="C:\TFS\AE.Web.ezHealthQuoter.Common\1_Dev\Shared\Pdfs" />
</Grid>
FileSystemBrowser.xaml
<UserControl x:Class="PdfExample.FileSystemBrowser"
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" xmlns:my="clr-namespace:PdfExample">
<DockPanel>
<my:FileSystemTree x:Name="fileSystemTree1" RootPath="{Binding Path=RootPath}" Width="150" />
<ListBox DockPanel.Dock="Right" />
</DockPanel>
FileSystemBrowser.xaml.cs
public partial class FileSystemBrowser : UserControl
{
#region Static Members
static FileSystemBrowser()
{
PropertyChangedCallback rootPathChangedCallback = new PropertyChangedCallback(OnRootPathChanged);
PropertyMetadata metaData = new PropertyMetadata(rootPathChangedCallback);
RootPathProperty = DependencyProperty.Register("RootPath", typeof(string), typeof(FileSystemBrowser), metaData);
}
static DependencyProperty RootPathProperty;
public static void OnRootPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FileSystemBrowser).RootPath = e.NewValue as string;
}
#endregion
public string RootPath
{
get { return this.ViewModel.RootPath; }
set { this.ViewModel.RootPath = value; }
}
public FileSystemBrowserViewModel ViewModel
{
get;
protected set;
}
public FileSystemBrowser()
{
InitializeComponent();
this.ViewModel = new FileSystemBrowserViewModel();
this.DataContext = this.ViewModel;
}
}
public class FileSystemBrowserViewModel : INotifyPropertyChanged
{
private string _rootPath;
public string RootPath
{
get { return _rootPath; }
set { _rootPath = value; RaisePropertyChanged("RootPath"); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
FileSystemTree.xaml
<UserControl x:Class="PdfExample.FileSystemTree"
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">
<DockPanel>
<TreeView SelectedValuePath="{Binding Path=SelectedValuePath, Mode=TwoWay}" HorizontalAlignment="Stretch" Name="treeView1" VerticalAlignment="Stretch" ItemsSource="{Binding RootFolder}" HorizontalContentAlignment="Left" VerticalContentAlignment="Top" Margin="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}">
<TextBlock Text="{Binding FolderName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
FileSystemTree.xaml.cs
public partial class FileSystemTree : UserControl, INotifyPropertyChanged
{
#region Static Members
static DependencyProperty RootPathProperty;
static FileSystemTree()
{
PropertyChangedCallback rootPathChangedCallback = new PropertyChangedCallback(OnRootPathChanged);
PropertyMetadata metaData = new PropertyMetadata(rootPathChangedCallback);
RootPathProperty = DependencyProperty.Register("RootPath", typeof(string), typeof(FileSystemTree), metaData);
}
public static void OnRootPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FileSystemTree).RootPath = e.NewValue as string;
}
#endregion
public string RootPath
{
get { return this.ViewModel.RootPath; }
set { this.ViewModel.RootPath = value; }
}
public FileSystemTreeViewModel ViewModel
{
get;
protected set;
}
public FileSystemTree()
{
InitializeComponent();
this.ViewModel = new FileSystemTreeViewModel();
this.DataContext = this.ViewModel;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public class FileSystemTreeViewModel : INotifyPropertyChanged
{
public IFolder[] RootFolder
{
get
{
if (RootPath != null)
return new IFolder[] { new FileSystemFolder(RootPath) };
return null;
}
}
private string _rootPath;
public string RootPath
{
get { return _rootPath; }
set
{
_rootPath = value;
RaisePropertyChanged("RootPath");
RaisePropertyChanged("RootFolder");
}
}
private string _selectedValuePath;
protected string SelectedValuePath
{
get { return _selectedValuePath; }
set { _selectedValuePath = value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I know that the tree works, because if I just put the tree inside MainWindow.xaml, it's fine. But for some reason, the RootPath value from MainWindow.xaml gets bound into FileSystemBrowser and stops there. It never makes it all the way down to FileSystemTree. What am I missing?
There is to few information to be certain, but I think the problem is the DataContext that is not set. Try relative bindings, this will probably help. In FileSystemBrowser.xaml change the binding as follows:
<my:FileSystemTree x:Name="fileSystemTree1"
RootPath="{Binding Path=RootPath,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=UserControl}}"
Width="150" />
Another possibility would be to set the UserControls this-reference to the DataContext. This should also help.