c# wpf mvvm prism regionamanger property is null in Bootstrapper - wpf

Im trying to create a new WPF MVVM prism project, but when i try to add view to my region in shell there is a null reference exception for RegionManager property. Can anybody help me ?
here is my code:
class Bootstrapper : UnityBootstrapper
{
public IRegionManager regionManager { get; set; }
public IRegionManager RegionManager
{
get
{
return regionManager;
}
set
{
regionManager = value;
}
}
protected override DependencyObject CreateShell()
{
return new Shell();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
this.RegionManager.RequestNavigate("MainRegion", new Uri("PartNumberView", UriKind.Relative));
}
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
}
}
shell xaml:
<Window x:Class="MechanicsPriceComparer.Shell"
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:prism="http://www.codeplex.com/prism"
xmlns:local="clr-namespace:MechanicsPriceComparer"
mc:Ignorable="d"
Title="MechanicPriceComparer" Height="450" Width="800">
<Grid>
<ItemsControl Name="NameOfMainRegion" prism:RegionManager.RegionName="MainRegion" ></ItemsControl>
</Grid>
</Window>
app.xaml:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}

You need to initialize the RegionManager property before using it.
Example for your code:
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
RegionManager = Prism.Regions.RegionManager.GetRegionManager(Application.Current.MainWindow);
this.RegionManager.RequestNavigate("MainRegion", new Uri("PartNumberView", UriKind.Relative));
}

Related

UI component does not exist in current context when code moved to class from main UI thread

I currently have working code which is implemented in MainWindow.xaml.cs that I am trying to move to a class which is giving me an error that my UI label does not exist in the current context.
Here is the code that works in the MainWindow:
public partial class MainWindow : Window
{
......
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { Consumer(consumer);}, TaskCreationOptions.LongRunning);
}
}
public void Consumer(Consumer consumer)
{
while (true)
{
Thread.Sleep(1000);
.......
Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
Then I tried moving the code to a separate class:
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
}
}
public class RX
{
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
}
I'm getting the error on:
mylbl.Content = value.ToString();
from the class RX. I tried this as recommended var m_dispatcher = Application.Current.MainWindow to get to the MainWindow thread but its still giving an error.
You cannot access mylbl from other classes , other than MyWindow since it is defined there .
You can implement MVVM and bind the content property to string in view model and update the content .
or segregate your business logic to separate class and expose this to MyWindow.Xaml.cs.
You can have a public method which returns "value" in RX . and you can update your content in MyWindow.xaml.cs by accessing this method
or pass Label instance to ProcessMessage method and update the content. Of course,add a reference System.Windows.Controls in your class.
However this is not a good design . I suggest you to go through MVVM.
public void ProcessMessages(Label mylbl)
{
while (true)
{
Thread.Sleep(1000);
....
var m_dispatcher = Application.Current.MainWindow;
m_dispatcher.Dispatcher.BeginInvoke(new Action(() =>
{
mylbl.Content = value.ToString();
}), DispatcherPriority.Background);
}
}
and caller will look like this
public partial class MainWindow : Window
{
....
private RX consumer = new RX();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Task backgroundDBTask = Task.Factory.StartNew(() => { consumer.ProcessMessages(mylbl); }, TaskCreationOptions.LongRunning);
}
}
}
As suggested by Clemens , i am updating solution in MVVM way.
XAML Part:
<Window x:Class="MvvmExample.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:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MvvmExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid >
<Label Content="{Binding LableContent}" Height="100" Width="500" Foreground="Red"/>
</Grid>
</Window>
I am binding LableContent string property to Content property of Label. And setting data content at the top to my View Model. Also,to bind event to command i have used interactivity dll.
ViewModel will look like this.
public class ViewModel : INotifyPropertyChanged
{
#region Constants and Enums
#endregion
#region Private and Protected Member Variables
private string _lableContent;
#endregion
#region Private and Protected Methods
private void OnLoaded(object obj)
{
Task.Factory.StartNew(() => { ProcessMessages(); }, TaskCreationOptions.LongRunning);
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region Constructors
public ViewModel()
{
LoadedCommand = new RelayCommand(OnLoaded);
}
#endregion
#region Public Properties
public event PropertyChangedEventHandler PropertyChanged;
public string LableContent
{
get
{
return _lableContent;
}
set
{
_lableContent = value;
OnPropertyChanged(nameof(LableContent));
}
}
public ICommand LoadedCommand { get; }
#endregion
#region Public Methods
public void ProcessMessages()
{
while (true)
{
Thread.Sleep(1000);
LableContent = "your value field";
}
}
#endregion
}
I have used ICommand implementation for commands .
Also i have used INotifyPropertyChanged for binding .
I assume you have the knowledge about following topics , If not there are plenty of help available in stack overflow on these
INotifyProertyChanged
Event to command binding
What is data context and how to set the data context
what is ICommand and implementing ICommand

Gui not updating if property of other class object changed

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!

WPF issue with a resizable / collapsible usercontrol

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 :)

My static Model/Property is not binding to my UI

New to WPF.
I am trying to bind my Model to my UI. So, when the Property is changed during my User actions I want the field to update whereever it occurs on my UI.
This is my Model:
namespace WpfApplication1
{
public class model2
{
private static string myField2;
public static string MyField2
{
get { return myField2; }
set { myField2 = value; }
}
}
}
My Markup:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:model2 x:Key="mymodel"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Source={StaticResource ResourceKey=mymodel}, Path=MyField2}"></TextBlock>
<Button Content="static test!" Click="Button_Click_1" />
</StackPanel>
</Grid>
</Window>
My code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
model2.MyField2 = "static!";
}
}
}
The field on the UI does not change?
You need to notify changes to the UI so it can update with new values.
In your case you want to notify static properties of changes so you would need a static event. The problem is the INotifyPropertyChanged interface needs a member event so you won't be able to go that way.
You best shot is to implement the Singleton pattern:
namespace WpfApplication1
{
public class model2 : INotifyPropertyChanged
{
//private ctor so you need to use the Instance prop
private model2() {}
private string myField2;
public string MyField2
{
get { return myField2; }
set {
myField2 = value;
OnPropertyChanged("MyField2");
}
}
private static model2 _instance;
public static model2 Instance {
get {return _instance ?? (_instance = new model2();)}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then make your property a member property and bind like this:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:model2 x:Key="mymodel"/>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Source={x:Static local:model2.Instance}, Path=MyField2}"/>
<Button Content="static test!" Click="Button_Click_1" />
</StackPanel>
</Grid>
</Window>
Code behind:
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
model2.Instance.MyField2 = "static!";
}
}
}
Use the Static extension to bind the TextBlocks Text Property:
<TextBlock Text="{Binding Source={Static MyModel.MyField2}, Mode=TwoWay">
But still the Property must raise the PropertyChanged event. My understanding why you use the static field is to be able to set the value from somewhere else. Have you thougt about using messages instead? Checkout the MVVM Light toolkit and the messenger. This would decouple the two components
I think that static properties are not what you want to use, from comments I can deduce that you are using only to make your program work. Below is the full working code.
App.xaml
Remove the code StartupUri="MainWindow.xaml instead we will instantiate MainWindow in code-behind to provide DataContext.
App.xaml.cs
Here we are assigning object of Model2 as Window.DataContext and then showing the window.
public partial class App : Application
{
public App()
{
Model2 model = new Model2();
MainWindow window = new MainWindow();
window.DataContext = model;
window.Show();
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
//We get hold of `DataContext` object
var model = this.DataContext as Model2;
model.MyField2 = "Hello World";
}
}
Model:
public class Model2 : INotifyPropertyChanged
{
private string _myField2;
public string MyField2
{
get { return _myField2; }
set
{
_myField2 = value;
OnPropertyChanged("MyField2");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainWindow.xaml
<Window x:Class="WpfApplication2.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">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding MyField2}"></TextBlock>
<Button Content="static test!" Click="ButtonBase_OnClick" />
</StackPanel>
</Grid>
</Window>
And I checked, it works !

WPF Nested Usercontrol bindings

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.

Resources