I have the below visibility binding that is throwing:
System.Windows.Data Error: 40 : BindingExpression path error: 'Vis'
property not found on 'object' ''LoginViewModel' (HashCode=22943289)'.
BindingExpression:Path=Vis; DataItem='LoginViewModel'
(HashCode=22943289); target element is 'Login' (Name=''); target
property is 'Visibility' (type 'Visibility')
Can't see what I've done wrong, the property does exist in the MainViewModel. Maybe I'm going about showing and hiding this the wrong way.
<Window x:Class="Bt.MainWindow"
xmlns:vm="clr-namespace:Bt"
xmlns:ctrls="clr-namespace:Bt.Controls">
<Window.DataContext>
<vm:MainViewModel x:Name="MWin" />
</Window.DataContext>
<Grid>
<ctrls:Login Visibility="{Binding Vis}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
</Grid>
</Window>
ViewModel:
namespace Bt
{
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Vis = "Collapsed";
}
private string _vis = "Collapsed";
public string Vis
{
get { return _vis; }
set
{
_vis = value;
RaisePropertyChanged("Vis");
}
}
}
}
[EDIT] Capturing inside the User Control, when the User Control's visibility is changed in the Main window.
I realize that the converter is not being called correctly, so may need some help there as well. As for the rest hopefully you can see what I'm trying to achieve.
View:
<UserControl x:Class="Bt.Controls.Login"
xmlns:app="clr-namespace:Bt"
xmlns:viewmodel="clr-namespace:Bt.Controls"
mc:Ignorable="d"
Visibility="{Binding Visi,Converter={StaticResource BooleanToVisibilityConverter}}"
>
</UserControl>
View Model:
namespace Bt.Controls
{
class LoginViewModel : INotifyPropertyChanged
{
public LoginViewModel(){}
private bool _visi = true;
public bool Visi
{
get { return _visi; }
set
{
_visi = value;
RaisePropertyChanged("Visi");
MessageBox.Show("Visi set");
reset_timer(_visi);
}
}
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public const string Invert = "Invert";
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a Visibility.");
bool? bValue = (bool?)value;
if (parameter != null && parameter as string == Invert)
bValue = !bValue;
return bValue.HasValue && bValue.Value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return ((value is Visibility) && (((Visibility)value) == Visibility.Visible));
}
#endregion
}
}
As evident from error BindingEngine is looking for property Vis in LoginViewModel and not in MainViewModel. (You must have set DataContext for your Login UserControl to LoginViewModel).
You need to get Window's DataContext which you can get using RelativeSource:
<ctrls:Login Visibility="{Binding DataContext.Vis,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
Also you should create Vis as bool and use BooleanToVisibility converter in your binding.
<Grid>
<Grid.Resources>
<BooleanToVisibilityConverter x:Key="BooelanToVisibilityConverter"/>
</Grid.Resources>
<ctrls:Login Visibility="{Binding DataContext.Vis,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}}",
Converter={StaticResource BooelanToVisibilityConverter}
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</Grid>
I would discourage use of Visibility property in ViewModel because it's View thing which should not be there in ViewModel. Having bool is perfectly fine which you can always convert using converter.
Keep Vis as type of Visibility
class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
}
private Visibility _vis = Visibility.Collapsed;
public Visibility Vis
{
get { return _vis; }
set
{
_vis = value;
RaisePropertyChanged("Vis");
}
}
}
Also specify the Source for the binding,
<Window x:Class="Bt.MainWindow"
xmlns:vm="clr-namespace:Bt"
xmlns:ctrls="clr-namespace:Bt.Controls">
<Window.Resources>
<vm:MainViewModel x:Key="MWin" />
</Window.Resources>
<Grid>
<ctrls:Login Visibility="{Binding Vis, RelativeSource={StaticResource MWin}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></ctrls:Login>
</Grid>
</Window>
As Rohit says, you could use BooleanToVisibility instead of changing the property as Visibility..
Related
i have a problem converting a string to a icon. The icon Geometry is in a ResourceDictionary. The ValueConverter is not called (i tried to debug in the Convert Method of the Converter). Here is my code:
xaml:
<Window.Resources>
<local:StatusToPathDataConverter x:Key="PathConverter"/>
</Window.Resources>
<Grid>
<Path Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource PathConverter}}"/>
</Grid>
cs:
public partial class MainWindow :Window {
public MainWindow() {
InitializeComponent();
}
public string Status
{
get { return (string)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(MainWindow));
}
public class StatusToPathDataConverter :IValueConverter {
private static ResourceDictionary iconDictionary;
public ResourceDictionary IconDictionary
{
get
{
if(iconDictionary == null) {
iconDictionary = new ResourceDictionary();
iconDictionary.Source = new Uri("/WPFBindingTest;component/Resources/IconDictionary.xaml", UriKind.RelativeOrAbsolute);
}
return iconDictionary;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var status = (string)value;
if(statinStatus == null)
return null;
switch(status.ToLower()) {
case "test":
return IconDictionary["TestIcon"];
// ...
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
You're not binding to anything. You need to tell the Binding to go to the Window to find the Status property. The recommended way to do that is to use RelativeSource, as below:
<Path
Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource PathConverter}}"
/>
As #Clemens notes, UpdateSourceTrigger=PropertyChanged doesn't make any sense on this binding and should not be there. That attribute tells the Binding when it should update the binding's source property. The source property is Window.Status, in this case.
However, the Path.Data property does not update the property it's bound to. A Path displays a Geometry; it doesn't edit a Geometry. UpdateSourceTrigger exists for control properties that update viewmodel properties, like TextBox.Text. That's the most common use for UpdateSourceTrigger=PropertyChanged: By default TextBox.Text updates the source property when the TextBox loses focus, but sometimes you want it to update on each keystroke.
Set the DataContext of the window to itself for the binding to work and the Convert method of the converter to get called:
public MainWindow() {
InitializeComponent();
DataContext = this;
}
If the binding to the source property fails the converter will never be invoked.
I have inherited a class, MyModernWindow from Window, and added a property and dependency property called MyTitleLinks. The type is MyLinkCollection : ObservableCollection<MyLink>. In XAML, I'm trying to define the MyTitleLinks, and bind the MyLink.Command property to a property in my Window's ViewModel.
I have tried numerous ways to bind, including FindAncestor and ElementName, and I am constantly unsuccessful.
If using {Binding AboutCommand} or {Binding DataContext.AboutCommand, ElementName=mainWindow}, I get this error in the Output:
Cannot find governing FrameworkElement or FrameworkContentElement for target
element. BindingExpression:Path=AboutCommand; DataItem=null; target
element is 'MylLink' (HashCode=30245787); target property is 'Command'
(type 'ICommand')
If using {Binding DataContext.AboutCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MyModernWindow}}},
Cannot find source for binding with reference 'RelativeSource
FindAncestor,
AncestorType='My.Namespace.MyModernWindow',
AncestorLevel='1''. BindingExpression:Path=DataContext.AboutCommand;
DataItem=null; target element is 'MyLink' (HashCode=35075009); target
property is 'Command' (type 'ICommand')
MainWindow.xaml
<local:MyModernWindow x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My.Controls"
IsTitleVisible="True"
Style="{StaticResource MyModernWindow}"
Title="My Window"
WindowStartupLocation="CenterScreen">
<local:MyModernWindow.MyTitleLinks>
<local:MyLink DisplayName="Support" Source="https://www.google.com/support/" />
<local:MyLink DisplayName="About" Command="{Binding AboutCommand}" />
</local:MyModernWindow.MyTitleLinks>
</local:MyModernWindow>
MainWindow.xaml.cs
public partial class MainWindow : MyModernWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
}
MyLinkCollection Class
public class MyLinkCollection : ObservableCollection<MyLink>
{
}
MyLink Class
public class MyLink : DependencyObject
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(MyLink));
public static readonly DependencyProperty DisplayNameProperty = DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(MyLink));
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(Uri), typeof(MyLink));
public Uri Source
{
get { return (Uri)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set { SetValue(DisplayNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public MyLink()
{
SetCurrentValue(VisibilityProperty, Visibility.Visible);
}
}
ViewModel
public class MainWindowViewModel
{
public ICommand AboutCommand { get; private set; }
public MainWindowViewModel()
{
this.AboutCommand = new RelayCommand(OpenAboutWindow);
}
private void OpenAboutWindow(object o)
{
ModernDialog.ShowMessage("About Screen", "About", MessageBoxButton.OK);
}
}
What am I missing?
With the help of this blog post, I figured it out. Since MyLink and MyLinkCollection aren't in the visual tree, I used a "Proxy Element" to give a context.
I gave my Window a name, created a FrameworkElement, then created a hidden ContentControl. That's all I needed.
Here's the working XAML:
<local:MyModernWindow x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My.Controls"
x:Name="Window"
IsTitleVisible="True"
Style="{StaticResource MyModernWindow}"
Title="My Window"
WindowStartupLocation="CenterScreen">
<local:MyModernWindow.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding DataContext, ElementName=Window}" />
</local:MyModernWindow.Resources>
<ContentControl Visibility="Collapsed" Content="{StaticResource ProxyElement}"/>
<local:MyModernWindow.MyTitleLinks>
<local:MyLink DisplayName="Support" Source="{Binding DataContext.SupportSource, Source={StaticResource ProxyElement}}" />
<local:MyLink DisplayName="About" Command="{Binding DataContext.AboutCommand, Source={StaticResource ProxyElement}}" />
</local:MyModernWindow.MyTitleLinks>
</local:MyModernWindow>
The reason for the problem is that the DataContext is not inherited from the collection nor from the MyLink item.
To have WPF automatically managing the inheritance for you without the need of a proxy element you need to add "Freezable" at each step of your tree as follows:
public class MyLinkCollection : FreezableCollection<MyLink>
{
}
and
public class MyLink : Freezable
{
// class body
}
Xaml Behaviors Wpf(a Microsoft released project) uses the same approach to propagate the DataContext inside a Xaml defined collection without the need of additional proxies
I'm a true beginner in MVVM pattern. I'm trying to change the backgound of a grid on button's click. I have a xaml with a grid containing a button, and a ViewModel .cs from where i want to change the grid's background on button's click. Until there i just succeed to show up a MessageBox when i clicked...
.xaml code:
<Window x:Class="WpfSimple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimple"
Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Button Content="Click"
Height="23"
HorizontalAlignment="Left"
Background="Gray"
Margin="75.944,47.465,0,0"
Name="btnClick"
VerticalAlignment="Top"
Width="203"
Command="{Binding ButtonCommand}"/>
<!--What is necessary to add for changing grid color ? Commandparameter ?-->
</Grid>
MainWindowViewModel.cs code:
namespace WpfSimple
{
class MainWindowViewModel
{
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand(new Action<object>(ChangeBgColor));
}
public void ChangeBgColor(object obj)
{
/*HERE I WANT TO CHANGE GRID COLOR*/
}
}
}
Sorry for my bad english.
Best regards.
Fitst of all you should implement INotifyPropertyChanged in your ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, add NotifyPropertyChanged() to your properties setter.
Ok. Next, add new Property with your grid background color to your ViewModel:
private Brush _gridBackground;
public Brush GridBackground
{
get { return _gridBackground; }
set
{
_gridBackground = value;
NotifyPropertyChanged();
}
}
And bind your grid's background to your property:
<Grid Background="{Binding GridBackground}">
Finally you can just change GridBackground in the command handler:
public void ChangeBgColor(object obj)
{
GridBackground = Brushes.Blue;
}
You should remember that it's a bad practice to add WPF-classes like Brush to your code. The better way is to use IValueConverter in XAML code and BCL classes in your ViewModel. For example, you can use enumeration in ViewModel and convert it to brush in ValueConverter.
Add new enum for your ViewModel's property:
public enum GridState { Valid, Invalid }
Change property type:
private GridState _gridBackground;
public GridState GridBackground
{
get { return _gridBackground; }
set
{
_gridBackground = value;
NotifyPropertyChanged();
}
}
Add new class with value converter
public class GridStateToBackgroundColorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
GridState val = (GridState) value;
if(val == GridState.Valid)
return Brushes.Green;
return Brushes.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Add new static resource to your control
<UserControl.Resources>
<converters:GridStateToBackgroundColorConverter x:Key="gridStateToBackgroundColorConverter" />
</UserControl.Resources>
Update binding to your property
<Grid Background="{Binding GridBackground, Converter={StaticResource gridStateToBackgroundColorConverter}">
If you want to change the grid background color then you can use command parameter. You can pass any UI control as Command parameter. In your case pass grid to access grid in your view model.
Give the name to your grid and use that name to use as command parameter.
For this you need to implement the code like this:
<Window x:Class="WpfSimple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimple"
Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid Name="grid">
<Button Content="Click"
Height="23"
HorizontalAlignment="Left"
Background="Gray"
Margin="75.944,47.465,0,0"
Name="btnClick"
VerticalAlignment="Top"
Width="203"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding Elementname="grid"}"/>
</Grid>
After making this change to the .xaml file. Implement Parameterized Relay Command to use this passed Grid to use in your Viewmodel file.
To Implement Parameterized Relay Command Try to Implement following code:
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand<Grid>(ChangeBgColor);
}
public void ChangeBgColor(Grid grid)
{
if(grid!=null)
grid.Background = Brushes.Red; //Any color you want to change.
}
I hope this will work. Thank you.
Is it correct that it is not currently possible to bind to any Nullable<T> in Universal XAML Apps?
I found this link from 2013:
https://social.msdn.microsoft.com/Forums/en-US/befb9603-b8d6-468d-ad36-ef82a9e29749/textbox-text-binding-on-nullable-types?forum=winappswithcsharp
Stating that:
Binding to nullable values isn't supported in Windows 8 Store apps. It just didn't make it into this release. We've already got bugs on this behavior for v.Next.
But can it really be that this has not been fixed yet?
My Binding:
<TextBox Text="{Binding Serves, Mode=TwoWay}" Header="Serves"/>
My Property:
public int? Serves
{
get { return _serves; ; }
set
{
_serves = value;
OnPropertyChanged();
}
}
And the error I get in my output:
Error: Cannot save value from target back to source.
BindingExpression:
Path='Serves'
DataItem='MyAssembly.MyNamespace.RecipeViewModel, MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'Text' (type 'String').
Seems like it's not fixed. As XAML is using a build-in converter, in this case you can probably exchange it with your own, dealing with nullables:
XAML:
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel.Resources>
<local:NullConverter x:Key="NullableIntConverter"/>
</StackPanel.Resources>
<TextBox Text="{Binding Serves, Mode=TwoWay, Converter={StaticResource NullableIntConverter}}" Header="Serves"/>
</StackPanel>
Code behind:
public class NullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{ return value; }
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
int temp;
if (string.IsNullOrEmpty((string)value) || !int.TryParse((string)value, out temp)) return null;
else return temp;
}
}
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private int? _serves;
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public int? Serves
{
get { return _serves; }
set { _serves = value; RaiseProperty("Serves"); }
}
public MainPage()
{
this.InitializeComponent();
DataContext = this;
}
}
Trying to get radiobuttons binding working but getting a run time error with the code below. Want the radio buttons to act such that only one can be selected at a time, and that they bind correctly in a 2 way fashion. Error text is.
"The invocation of the constructor on
type 'testapp1.MainWindow' that
matches the specified binding
constraints threw an exception"
<Window x:Class="testapp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:testapp1" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
<Label Content="{Binding Path=VeryLovelyEnum}" Height="28" Name="label1" />
</StackPanel>
</Grid>
</Window>
And code:
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
this.DataContext = _model;
}
}
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
public class TestModel : DependencyObject
{
public MyLovelyEnum VeryLovelyEnum
{
get { return (MyLovelyEnum)GetValue(VeryLovelyEnumProperty); }
set { SetValue(VeryLovelyEnumProperty, value); }
}
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
// from http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
}
The button & label is there from a previous test I did - I just left in...
The following declaration uses an integer instead of a default enum-value. Could be your instantiation problem...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
Try something like...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(MyLovelyEnum.FirstSelection));