I am trying to solve this issue for so many hours:
I have user custom control of grid named NewMazeGrid and I want to use it as a control in MainWindow. MainWindow contains MazeViewModel(mazeVM member).
I'm trying to set the values of the grid, when the property MazeViewModel:MySingleplay changes.
(I'm using the INotifyPropertyChanged for it, and it works perfectly fine. I guess, the problem is in the final binding)
The code:
This is the property MazeViewModel:MySingleplay getter:
public string MySingleplay
{
get
{
if (myModel.MySingleplay == null)
{
return "";
} else
{
return myModel.MySingleplay.ToString();//works perfect
}
}
}
this is the NewMazeGrid.xaml.cs:
namespace VisualClient.View.controls
{
public partial class NewMazeGrid : UserControl
{
private MazePresentation myMaze;
private string order; //dont really use it
//Register Dependency Property
public static readonly DependencyProperty orderDependency =
DependencyProperty.Register("Order", typeof(string), typeof(NewMazeGrid));
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
DataContext = this;
lst.ItemsSource = myMaze.MazePuzzleLists;
}
public string Order
{
get
{
return (string)GetValue(orderDependency);
}
set
{
SetValue(orderDependency, value);
myMaze.setPresentation(value); //(parsing string into matrix)
}
}
}
}
this is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
private MazeViewModel mazeVM;
public MainWindow()
{
InitializeComponent();
mazeVM = new MazeViewModel(new ClientMazeModel(new TCPClientConnection()));
DataContext = mazeVM;
mazeVM.connectToServer();
}
private void bu_Click(object sender, RoutedEventArgs e)
{
bool isC = mazeVM.isConnected();
mazeVM.openSingleplayGame("NewMaze");//works perfect
}
this is the MainWindow.xaml:
<Window x:Class="VisualClient.View.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:Controls ="clr-namespace:VisualClient.View.controls"
xmlns:vm ="clr-namespace:VisualClient.ViewModel"
xmlns:local="clr-namespace:VisualClient.View"
mc:Ignorable="d"
Title="Main Window" Height="350" Width="525" MinWidth="900" MinHeight="600">
<WrapPanel >
<Button Name ="bu" Content="Click_Me" Click="bu_Click"/>
<Grid Name="myGrid">
<Controls:NewMazeGrid Order="{Binding MySingleplay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</WrapPanel>
</Window>
I get this error on the binding line: Value cannot be null.
To sum:
It initialize fine the window in the ctor, but when the property changes it does not get into the Order property setter. therefor my grid never changes.
What should be the right syntax for binding in this case? how do I bind it to the right property?
Folders hierarchy explorer
WPF may not call the CLR wrapper of a dependency property, but just directly call the GetValue and SetValue methods of the underlying DependencyObject. This is why there should not be any logic except the GetValue and SetValue calls.
This is explained in XAML Loading and Dependency Properties:
Because the current WPF implementation of the XAML processor behavior
for property setting bypasses the wrappers entirely, you should not
put any additional logic into the set definitions of the wrapper for
your custom dependency property. If you put such logic in the set
definition, then the logic will not be executed when the property is
set in XAML rather than in code.
Similarly, other aspects of the XAML processor that obtain property
values from XAML processing also use GetValue rather than using the
wrapper. Therefore, you should also avoid any additional
implementation in the get definition beyond the GetValue call.
To get notified about property value changes, you can register a PropertyChangedCallback by property metadata. Note also that there is a naming convention for DependencyProperty fields. Yours should be called OrderProperty:
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register(
"Order", typeof(string), typeof(NewMazeGrid),
new PropertyMetadata(OnOrderChanged));
public string Order
{
get { return (string)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
private static void OnOrderChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((NewMazeGrid)obj).myMaze.setPresentation((string)e.NewValue);
}
Besides that, you must not set
DataContext = this;
in the constructor of NewMazeGrid. This effectively prevents inheriting the DataContext from the parent window, so that {Binding MySingleplay} won't work. Except under special circumstances you should never explicitly set a UserControl's DataContext.
So, remove the DataContext assignment from the constructor:
public NewMazeGrid()
{
myMaze = new MazePresentation();
InitializeComponent();
lst.ItemsSource = myMaze.MazePuzzleLists;
}
That said, there is also no need to set UpdateSourceTrigger=PropertyChanged on a one-way binding. It only has an effect in two-way (or one-way-to-source) bindings:
<Controls:NewMazeGrid Order="{Binding MySingleplay}"/>
Related
First off, of course I'm aware that this is one of the most repetitive questions to ask about... i'll save you a moment by saying that this has a twist.
First, the setup
<UserControl {d:DesignInstance Type=MyControl, IsDesignTimeCreatable=True}>
<Grid x:Name="root">
<FrameElement x:Name="ContextProxy" Visibility="Collapsed" />
...
<DataGridTextColumn.Visibility>
<!-- squiggly line on the SOURCE part -->
<Binding Source="{x:Reference Name=ContextProxy}"
Path=DataContext.IncludeThisColumn
/>
</DataGridTextColumn.Visibility>
</Grid>
</UserControl>
codebehind
public partial class MyControl {
// viewmodel for INPC (data lookups, child controls, etc)
public ViewModels.vmMyControl { get; } = new ViewModels.vmMyControl();
MyControl() { // ctor
this.root.DataContext = this; // to support DependencyProperty
}
// several dependency properties for external use/bindings
public static DP IncludeThisColumnDP = DP.Register(..., OnIncludeThisColumnChanged)
public bool IncludeThisColumn { GetValue(DP); } { SetValue(DP, value); }
// relay DP changes from parent / outside world, to internal ViewModel
private void OnIncludeThisColumnChanged(..)
{ (o as vmMyControl).ViewModel.IncludeThisColumn = e.NewValue; }
}
viewmodel
public class vmMyControl {
private readonly myObservableINPC<bool> _IncludeThisColumn;
public bool IncludeThisColumn { get/set for _IncludeThisColumn.Value }
public vmMyControl() { // ctor
// initialize INPC crap
this.IncludeThisColumn = new blah(nameof(IncludeThisColumn));
// data for designer
if (...GetIsInDesignMode)
Data_LoadMock();
}
public Data_LoadMock() {
// populate some of the data points
}
}
so generally speaking, I think I'm following best practices (other than mixing of DP/INPC, but it addresses my needs well, so whatever)...
user control's data context is applied to an internal root element (the top-level Grid)
Data context set to instance of usercontrol to enable DP.
user control instance has readonly property initialized to ViewModel
viewmodel uses GetIsInDesignMode to ensure data doesn't load (and actually, the code's DAL proxies also check an internal bool for indicating when they're being run as a unit test, to further confirm that they're disabled, with InternalsVisibleTo(UnitTestProject))
since datagrid column visibility can't be bound directly (not in the element tree or whatever), I added a FrameworkElement to act as a proxy to datacontext
The twist
So, first let me say that at runtime, the code works. All of the bindings, data loads, etc. No exceptions. This is purely a design-time issue, but it bugs me.
Since I'm aware of the null reference issues when dealing w/ WPF design mode, my first action when I saw the issue was to add:
MyControlTests.cs
[TestMethod]
public void InstantiateVM() {
Action a = () => { return new vmMyControl(); }
a.ShouldNotThrow(); // FluentAssertions
}
[TestMethod]
public void InstantiateUC() {
Action a = () => { return new MyControl(); }
a.ShouldNotThrow(); // FluentAssertions
}
here's the thing... both of the tests run successfully... and upon inspection (debug + breakpoint), no properties in the object result in an exception.
Environment
App is using framework 4.5 and C#6 (since i'm on VS2015)
I saw someone post about VS2015 hovering over the message to get more info, but it doesn't seem to do anything for me.
For what it's worth, I'm using VS2015 REL... was tempted to apply update 2 or 3, but there seem to be a few bugs here and there from what I'm hearing/reading.
As per http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
then
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
and
<DataGridTemplateColumn.Visibility>
<Binding Source="{StaticResource ResourceKey=DataContextProxy}"
Path="Data.IncludeThisColumn"
Converter="{StaticResource ResourceKey=BoolToHiddenConverter}"
/>
</DataGridTemplateColumn.Visibility>
I have an application which has CarViewModel + view (UserControl).
What I want to achieve is to change the style of brushes when the bound DataContext Car.Status changes.
I found out how to change the brushes (in code behind of the view):
private void LoadThemeResources(bool isPrepareMode)
{
if (isPrepareMode)
{
Uri themeUri = new Uri(#"/../Resources/MyBrushes.Light.xaml", UriKind.Relative);
ResourceDictionary themeDictionary = Application.LoadComponent(themeUri) as ResourceDictionary;
this.Resources.MergedDictionaries.Add(themeDictionary);
}
else
{
this.Resources.MergedDictionaries.Clear();
}
}
By default the application and everthing has a dark theme spread over multiple files. This MyBrushes.Light overwrites some of those.
But I have no clue how I can execute the LoadThemeResources function based on a property change in the ViewModel in a MVVM friendly way.
I can do in the code behind of the view:
var vm = (CarViewModel) DataContext;
vm.Car.PropertyChanged += HandleStatusChanged;
But this is a tight coupling between View and ViewModel.
I can also do it via Messenger (From MVVM Light), but that gets broadcasted throughout the whole application and seems overkill.
Is there an other way? Or preferred way?
I would prepare some attached property (used on UserControl). Bind that property to your view-model and add code logic of LoadThemeResources in the property changed callback, something like this:
public static class ThemeService {
public static DependencyProperty IsPrepareModeProperty =
DependencyProperty.RegisterAttached("IsPrepareMode", typeof(bool), typeof(ThemeService),
new PropertyMetadata(isPrepareModeChanged));
public static bool GetIsPrepareMode(UserControl e){
return (bool) e.GetValue(IsPrepareModeProperty);
}
public static void SetIsPrepareMode(UserControl e, bool value){
e.SetValue(IsPrepareModeProperty, value);
}
static void isPrepareModeChanged(object sender, DependencyPropertyChangedEventArgs e){
var u = sender as UserControl;
u.LoadThemeResources((bool)e.NewValue);
}
}
//you need some public method of LoadThemeResources
public void LoadThemeResources(bool isPrepareMode) {
//...
}
Usage in XAML:
<UserControl ...
local:ThemeService.IsPrepareMode="{Binding Car.Status}">
<!-- ... -->
</UserControl>
You can also declare a normal DependencyProperty for your UserControl's class and use that instead of the attached property (the usage is just the same).
You could bind to a property on your ViewModel, and use an IValueConverter in your View to turn that property (whether boolean, status enumeration, whatever) into a Brush to be used.
That is, load the theme/resources in the converter (a deliberate bridge between View and ViewModel) so that your View gets the Brush it wants and your ViewModel only has to expose the 'important' information (the bits that help decide what brush to load). The decision logic is all in the converter.
I've been trying for ever to try and figure this out.
Story: I have one MainWindow and 2 User Controls.
When the MainWindow loads One control is visible and the other is not.
Once the user enters their data and settings, I need to make the other form visible.
The form that is invisible at startup needs to be initialized, because it is gathering data from the WMI of the computer it is running on. It is also gathering AD Information in preparation for the user.
For some reason I cannot get one form to show the other.
I think this is what I'm supposed to be looking at:
#region Class Variable
public string ShowSideBar { get { return (String)GetValue(VisibilityProperty); } set { SetValue(VisibilityProperty, value); }}
public DependencyProperty VisibilityProperty = DependencyProperty.Register("ShowSideBar", typeof(string), typeof(UserControl), null);
#endregion
This is set in my MainWindow Class, however, I have no idea why I cannot call it from any other usercontrol.
Is there any way to just expose something like this to all my forms from my MainWindow?
public int RowSpan {
get { return Grid.GetRowSpan(DockPanel1); }
set { Grid.SetRowSpan(DockPanel1,value); }
}
Dependency properties must be static. Why is the type string? Should it not be Visibility if you wish to bind the visibility of the controls to it?
Does it have to be a dependency property? You could just use a regular property as well and implement INotifyPropertyChanged, since you are not binding this field to anything, rather binding other things to it.
For a dependency property, try something like this instead:
public static readonly DependencyProperty SideBarVisibilityProperty = DependencyProperty.Register("SideBarVisibility", typeof(Visibility), typeof(MyTemplatedControl), null);
public Visibility SideBarVisibility
{
get { return (Visibility)GetValue(SideBarVisibilityProperty); }
set { SetValue(SideBarVisibilityProperty, value); }
}
Firstly, this application would benefit from application of the MVVM pattern.
However, without taking that approach, you can still resolve the problem you have. It would be unusual for a user control to rely on knowing what its parent is. The code behind for your main window would be the better place to put this code. (Not as good as a view model... but that's another story.)
Add to the control that should cause the side bar to be made visible an event, ShowSideBar. Attach a handler in the main window, and use the handler to display the second control. No need for dependency properties here at all.
public class MyControl : UserControl
{
...
public event EventHandler ShowSideBar;
// Call this method when you need to show the side bar.
public void OnShowSideBar()
{
var s = this.ShowSideBar;
if (s != null)
{
s(this, EventArgs.Empty);
}
}
}
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.FirstControl.ShowSideBar += (s, e) =>
{
this.SecondControl.Visibility = Visibility.Visible;
}
}
}
I fixed the initlized Component but changing.
X:Class="AdminTools.MainWindow.ShowSideBar" to x:Class="AdminTools.MainWindow".
now i have an issues where
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:User="clr-namespace:AdminTools.Controls.User"
xmlns:Custom="clr-namespace:AdminTools.Controls.Custom"
xmlns:Bindings="clr-namespace:AdminTools.Functions"
x:Class="AdminTools.MainWindow"
Title="MainWindow" Height="691.899" Width="1500"
>
<Window.DataContext>
<Bindings:ShowSideBar />
</Window.DataContext>
<Bindings:ShowSideBar /> = ShowSideBar does not exist in the namespace clr-namespace:AdminTools.Functions
ShowSideBar: member names cannot be the same as their enclosing type.
I'm creating a UserControl with a DependencyProperty but the DependencyProperty isn't getting the value that the caller is passing in.
I've discovered the following during my own investigation
If I use a built-in user control, such as TextBlock, everything works. This narrows the problem down to my UserControl's implementation (as opposed to the code that calls the UserControl)
My property changed callback that I register isn't even being called (well... at least the breakpoint isn't being hit)
If only see this problem when I use a binding to provide the dependency property, so this doesn't work:
<common:MyUserControl MyDP="{Binding MyValue}"/>
but I have no problems if I get rid of the binding and hardcode the value, so this works:
<common:MyUserControl MyDP="hardCodedValue"/>
Here's my UserControl's code behind:
public partial class MyUserControl : UserControl
{
public string MyDP
{
get { return (string)GetValue(MyDPProperty); }
set { SetValue(MyDPProperty, value); }
}
public static readonly DependencyProperty MyDPProperty =
DependencyProperty.Register(
"MyDP",
typeof(string),
typeof(MyUserControl),
new FrameworkPropertyMetadata(
"this is the default value",
new PropertyChangedCallback(MyUserControl.MyDPPropertyChanged)));
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyUserControl)obj).MyDP = (string)e.NewValue;
}
public MyUserControl()
{
InitializeComponent();
this.DataContext = this;
}
}
And here's the xaml
<Grid>
<TextBlock Text="{Binding MyDP}"/>
</Grid>
Since I'm able to use built-in user controls such as TextBlock, I don't think that the error lies in my host code, but here it is, just so that you have a complete picture:
<StackPanel>
<common:MyUserControl MyDP="{Binding MyValue}"/>
</StackPanel>
public class MainWindowViewModel
{
public string MyValue { get { return "this is the real value."; } }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
This line in the UserControl is wrong:
this.DataContext = this;
This makes the UserControl its own DataContext, so the binding is looking for a property called MyValue on the UserControl, and that property does not exist. You want the DataContext to be your view-model. If you don't set it explicitly, it will inherit the DataContext from its container (the Window in this case).
Delete that line, and you'll be closer. You also don't need that callback; remove that too.
You can update your control's view code like that:
<Grid>
<TextBlock x:Name="_textBlock"/>
</Grid>
And set a _textBlock's text property in MyDPPropertyChanged method:
public static void MyDPPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = ((MyUserControl)obj);
control.MyDP = (string)e.NewValue;
control._textBlock.Text = control.MyDP;
}
That will do the trick.
Kindly Implement INotifyPropertyChanged, and PropertyChangedEventHandler in side the control and also the viewmodel. secondly use SetCurrentValue method to set the value inside the control class rather setting it directly
So here is the problem I'm beating my head against: I have a custom user control that exposes two dependency properties that are bound to my ViewModel. In my ViewModel I have an instance of a class that holds multiple properties that express values that relate to the user control as well as to items that control manipulates. Here's a bit of sample code to explain it visually so here is a simple sample of my control, it's a Slider that is combined with a checkbox that allows the user to lock the slider.
<custom:SliderControl IsLocked="{Binding Path=CustomClass.IsLocked, Mode=TwoWay}" SliderValue="{Binding Path=CustomClass.Value, Mode=TwoWay}" />
IsLocked and SliderValue are dependency properties that effectively manipulate the checkbox and slider that are contained in the custom control. All of the control functions work as intended, except for the bindings to the class I've defined. If I create individual properties, as in one int property and one bool property the bindings work as intended. However I have five sliders, and each slider in my actual code has five properties that tie in to them. I'm trying to eliminate code duplication by creating a class to hold these properties in a reusable object shrinking my 25 properties down to 5 class instances.
My CustomClass inherits ObservableObject and has a bool property and int property named IsLocked and SliderValue respectively. For more visual aids here is what it looks like:
public class CustomClass : ObservableObject
{
public const string SliderValuePropertyName = "SliderValue";
private int _sliderValue= 0;
public int SliderValue
{
get
{
return _sliderValue;
}
set
{
if (_sliderValue== value)
{
return;
}
_sliderValue= value;
RaisePropertyChanged(SliderValuePropertyName );
}
}
public const string IsCheckedPropertyName = "IsChecked";
private bool _isChecked = false;
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
if (_isChecked == value)
{
return;
}
_isChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
}
}
The ViewModel property is very similar and looks like this, an new instance of the class is created when the ViewModel loads:
public const string SliderOnePropertyName = "SliderOne";
private CustomClass _sliderOne;
public CustomClass SliderOne
{
get
{
return _sliderOne;
}
set
{
if (_sliderOne== value)
{
return;
}
_sliderOne= value;
RaisePropertyChanged(SliderOnePropertyName );
}
}
Why won't the updating of the dependency property that is bound to the property in the class update properly? Is it because you can't properly update the class instance property by itself and instead have to update the entire class instance whenever changes occur? Or do I need to further customize the setter in this ViewModel property? As it sits now changing the slider value or checkbox never hits the bound property at all and nothing errors out when debugging.
EDIT: I've also surrounded the control in a Border and set the Border UIElement's DataContext to that of the class and then subsequently applied the more simple path binding to the underlying custom control. This however did not have any effect on my problem.
I'm a homegrown programmer so I often miss things when putting code together and I'm guessing this is the case here, unless what I'm trying just won't work.
Any help would be greatly appreciated.
EDIT: So I've been toying around with using a custom event that will let me know when the specific property of the custom control changes and then having that event wired up in my ViewModel to update the existing class. This works but still creates code duplication as now I have to have 10 events, 2 events per control, one to check for when the value of the slider changes and the other to detect when the checkbox IsChecked value changes. This code duplication exists since you can't route multiple command parameters (like a simple string identifier for which slider is being manipulated as well as the value you want to use in the code). This limitation means I can't just use 2 events that differentiate between which control is undergoing changes within the defined method as exposing the physical control to the ViewModel breaks the MVVM pattern. Using a class as the datacontext for the user control made it so I didn't care what control was being manipulated as they each had their own class instance. Using events this unravels the MVVM pattern as now I need to know which of the five controls is being manipulated by the user.
It can't be this hard to use a class in property bindings. I have to be missing something remedial.
here is a full example:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public SliderValues slv { get; private set; }
public ViewModel()
{
slv = new SliderValues();
}
}
public class SliderValues : INotifyPropertyChanged
{
bool _isLocked = false;
public bool IsLocked
{
get { return _isLocked; }
set
{
_isLocked = value;
OnPropertyChanged("IsLocked");
}
}
int _theValue = 5;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged("TheValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Now the xaml:
<UserControl x:Class="TestBindings.MainPage"
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"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Slider Height="23" HorizontalAlignment="Left" Margin="114,138,0,0" Name="slider1" VerticalAlignment="Top" Width="100"
DataContext="{Binding slv}" Value="{Binding TheValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
May be there is just a syntactical error. Try this
{Binding Path=CustomClass.IsLocked, Mode=TwoWay}
Try this...<custom:SliderControl DataContext="{Binding CustomClass}" IsLocked="{Binding IsLocked, Mode=TwoWay}" SliderValue="{Binding Value, Mode=TwoWay}" />