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.
Related
I am having ValueConverter which I am trying to use.
All bindings, including DataGrid content are working correctly, but I can't apply Value converter neither to the DataGridColumns, not to other fields, because it's methods are not invoked at all.
Possible problematic place could be the fact that we are using Reactive UI as MVVM framework, so it can be a possible source of problem. I am setting Items Source of the DataGrid via Reactive UI bindings.
But I have tried to set something like on the view:
public partial class OrderTimeSheetUserControl : OrderTimeSheetBaseUserControl
{
public OrderTimeSheetUserControl()
{
this.DataContext = ViewModel; // Set data context in case it may have helped (but it did not)
InitializeComponent();
}
}
P.S.: I have tried built-in BooleanToVisibilityConverter, as well as writing my converter with inheritance of MarkupExtension class and slightly different method of usage. (IValueConverter with MarkupExtension)
Value Converter below:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToCollapsedVisibilityValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool == false)
{
throw new NotSupportedException($"Conversion from {value.GetType().Name} is not supported by {nameof(BoolToCollapsedVisibilityValueConverter)}.");
}
var boolValue = (bool) value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And it's usage in the XAML page:
<userControls:OrderTimeSheetBaseUserControl.Resources>
<wpfValueConverters:BoolToCollapsedVisibilityValueConverter x:Key="Conv" />
<DataGrid>
<DataGrid.Columns>
<DataGridComboBoxColumn Visibility="{Binding BackPayCategoryVisible, Converter={StaticResource Conv}}" Header="Backpay Cat."/>
</DataGrid.Columns>
</DataGrid>
Reactive UI Binding of the ItemsSource of the DataGrid in xaml.cs:
this.OneWayBind(ViewModel,
viewModel => viewModel.Employees,
view => view.PersonsInOrderDataGrid.ItemsSource)
.DisposeWith(disposable);
So, all properties are bound correctly, but value converters are not working.
A DataGridColumn doesn't inherit any DataContext by default, so your binding fails and that's why the converter is never invoked. This has nothing to do with ReactiveUI.
You can get the binding to work by using a Freezable as suggested in this blog post:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
XAML:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
...
<DataGridTextColumn Header="Backpay Cat." Binding="{Binding Price}"
Visibility="{Binding Data.BackPayCategoryVisible,
Converter={StaticResource Conv},
Source={StaticResource proxy}}"/>
<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding VmProp}" />
<TextBlock Text="{Binding Weight, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource weightConverter}}" />
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
In the MyViewModel I have regular property
private string vmProp;
public string VmProp
{
get
{
return "kg";
}
}
And Convertor class has DependencyProperty is:
public class WeightConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty RequiredUnitProperty = DependencyProperty.Register("RequiredUnit", typeof(string), typeof(WeightConverter), null);
public string RequiredUnit
{
get
{
return (string)this.GetValue(RequiredUnitProperty);
}
set
{
this.SetValue(RequiredUnitProperty, value);
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double dblValue;
if (double.TryParse(value.ToString(), out dblValue))
{
if (this.RequiredUnit == "kg")
{
return dblValue;
}
else
{
return dblValue * 10;
}
return dblValue;
}
return 0;
}
When I do binding in XAML the code works:
<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="kg"/>
But when I try to bind it to ViewModelProperty the 'RequiredUnit' object is always null.
How can I bind dependency property to ViewModel property?
The reason its null is because you are trying to bind to the view model property from the resources, but the datacontext is not available to the resource. In your output log, you must be getting a binding expression error. Have a look at the output window.
There are multiple ways to get this working.
One way is to give your window a name like x:Name="MainWindow" and then in your binding do like:
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, ElementName=MainWindow}" />
Another way would be to do it using Relative Source binding.
Put x:Name="leapold" to your Window/Usercontrol
and make your binding with x:reference
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, Source={x:Reference leapold}}"/>
In WPF project I use MVVM pattern.
I try to bind an item in a collection to UserControl but everything gets default value of DependcyProperty.
The Window xaml:
<ListView VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Sessions}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Name="DebugTextBlock" Background="Bisque"
Text="{Binding Connection}"/>
<usercontrol:SessionsControl Model="{Binding Converter=
{StaticResource DebugConverter}}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Where Sessions is
private ObservableCollection<SessionModel> _sessions;
public ObservableCollection<SessionModel> Sessions
{
get { return _sessions; }
set
{
if (Equals(value, _sessions)) return;
_sessions = value;
OnPropertyChanged("Sessions");
}
}
The SessionModel:
public class SessionModel:ViewModelBase
{
private string _connection;
public string Connection
{
get { return _connection; }
set
{
if (value == _connection) return;
_connection = value;
OnPropertyChanged("Connection");
}
}
}
In the SessionsControl I create DependencyProperty:
//Dependency Property
public static readonly DependencyProperty ModelProperty =
DependencyProperty.Register("Model", typeof(SessionModel),
typeof(SessionsControl), new PropertyMetadata(new SessionModel("default_from_control")));
// .NET Property wrapper
public SessionModel Model
{
get { return (SessionModel)GetValue(ModelProperty); }
set { if (value != null) SetValue(ModelProperty, value); }
}
and use this xaml to display connection in form:
<TextBlock Name="DebugControlTextBlock" Background="Gray" Text="{Binding Connection}"/>
So, when I run application
var windowModel = new WindowsModel();
var window = new SessionWindow(windowModel);
window.ShowDialog();
I always get default_from_control value in DebugControlTextBlock, but in DebugTextBlock get the_real_connection
Even if I set breakpoint in DebugConverter I see that value is default.
The DebugConverter is simply wrapper to check correct binding:
public class DebugConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("DebugConverter: " + (value!=null?value.ToString():"null"));
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
See solution on github.
So, what happend when I Binding model to DependcyProperty?
I would suggest that you try to make you Sessions property a DependencyProperty too. Otherwise you might have to manually Raise PropertyChanged for Sessions property.
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..
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));