I have binding on some command:
<Button Command="{Binding Save}" />
Save is command of some object that can be selected from list.
In initial state there is no any selected object so binding does not work and CanExecute does not invoked. How can i disable this button using MVVM?
Solution: WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?
Guys, thanks for your answers and sorry for duplication of question.
Define a command that always return false to CanExecute. Declare it at a global position such as in your App.Xaml. you can specify this empty-command then as the FallbackValue for all your command bindings you expect a null value first.
<Button Command="{Binding Save,FallbackValue={StaticResource KeyOfYourEmptyCommand}}" />
You could create a trigger in XAML that disables the Button when the command equals x:Null.
An example can be found in the answer to this question: WPF/MVVM: Disable a Button`s state when the ViewModel behind the UserControl is not yet Initialized?
I'm not sure you'll be able to achieve this. However, an alternative would be to initialise the Command object initially with a basic ICommand where CanExecute simply returns False. You could then replace this when you're ready to put the real command in place.
Have a look at the Null Object Pattern
Create a NullToBooleanConverter and bind the IsEnabled property to the command, running it through the converter:
class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then
<UserControl.Resources>
<Extentions:NullToBooleanConverter x:Key="NullToBooleanConverter" />
</UserControl.Resources>
<Button Content="Hello" IsEnabled="{Binding Save, Converter={StaticResource NullToBooleanConverter}}" />
Related
We're developing a Xamarin Forms (v2.3.0.107) application with MVVMLight, primarily implemented in XAML.
For months we've had a LoginView in our app that uses DataTriggers to display the current step in a login procedure (Environment Selection -> Credentials -> Verification Code). The view has always done it's part thus far, but now it's not even showing any content, as if the DataTriggers are never triggered.
Nothing has been changed directly on this View and it's ViewModel, and most indirect changes are rather irrelevant to the issue.
The LoginView
<ContentPage
x:Class="MyProject.Views.LoginView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyProject.ViewModels;assembly=MyProject"
BindingContext="{Binding [LoginViewModel], Source={StaticResource ViewModelLocator}}">
<Grid>
<StackLayout
IsVisible="False">
<StackLayout.Triggers>
<DataTrigger
TargetType="StackLayout"
Binding="{Binding LoginStatus}"
Value="{x:Static viewmodels:LoginStatus.Login}">
<Setter
Property="IsVisible"
Value="True" />
</DataTrigger>
</StackLayout.Triggers>
...
</StackLayout>
...
</Grid>
</ContentPage>
The LoginViewModel
namespace MyProject.ViewModels
{
public enum LoginStatus
{
Login,
EnvironmentSelection,
InstanceSelection,
VerificationCode
}
public class LoginViewModel : BaseViewModel
{
private LoginStatus _loginStatus;
public LoginStatus LoginStatus
{
get { return _loginStatus; }
private set { Set(ref _loginStatus, value); }
}
public LoginViewModel()
{
LoginStatus = LoginStatus.Login;
}
}
}
What I've tried thus far
I use the following DebugConverter to debug binding:
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debugger.Break();
return value;
}
}
I've changed the binding for the StackLayout DataTrigger to:
Binding="{Binding LoginStatus, Converter={StaticResource DebugConverter}, ConverterParameter={x:Static viewmodels:LoginStatus.Login}}"
This allows me to verify that whatever I receive from the ViewModel is indeed the Value I provide in the DataTrigger. And indeed, both value and parameter parameters in the converter are the enum value MyProject.ViewModels.LoginStatus.Login. Even when I do a quick watch on value == parameter I get true as result.
Also, when I set the StackLayout's IsVisible to True, it DOES show me the content.
What does this tell me?
My LoginView's BindingContext bound to the LoginViewModel.
The Binding of my DataTrigger is indeed bound to LoginStatus and has the correct value (this shouldn't even change initially anyway).
The Value in my DataTrigger also returns the correct value.
The condition for DataTrigger is valid, so the Setter should trigger.
I've implemented such basic triggers a lot in WPF and it has ever since worked in Xamarin. How come this implementation suddenly decides fail? I must've checked the Setter implementation about 10 times, but I just cannot see any errors in the syntax.
Am I missing anything? According to search results this is not a common issue...
The source of the issue was my own stupid mistake...
I extended the our NavigationService to allow for an optional ViewModel to be defined, but I forgot to verify whether the handed ViewModel is not null. So after the ViewModelLocator initialized BindingContext, the NavigationService would override BindingContext with null.
I did not notice this behavior because the DebugConverter is not called when BindingContext is set to null. Instead it will invalidate any Triggers that utilize the BindingContext and revert any Setters that were called.
So I have a ProgressRing and a TextBlock and I am trying to implement this basic hack, which is to display both elements when TextBlock's Text gets assigned a value (anything other than null), else both elements should hide when TextBlock's Text is null.
My Xaml looks like below. TextBlock's Text is binded to MessageForProgressRing and its Visibility is binded to both MessageForProgressRing and TargetNullValue. Same for me ProgressRing:
<StackPanel Panel.ZIndex="100" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<mahControls:ProgressRing Height="50" IsActive="True" Width="50" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}" Foreground="White" Margin="0,0,0.2,0" />
<TextBlock Text="{Binding MessageForProgressRing}" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
</StackPanel>
Then, in code behind I just trigger the property and assign it a value on some button event handlers:
private void closeApplicationButtonTask()
{
((CaptureViewModel)DataContext).MessageForProgressRing = "Closing... ";
Application.Current.MainWindow.Close();
}
However, in my ViewModelBase (the parent of all my view models) it pops an error on OnPropertyChanged saying:
Requested value 'Closing...' was not found.
I think I need a converter because Visibility is binded to Closing... right? If yes how can I achieve it?
P.S I couldn't do it in OnPropertyChanged because I don't see the value to assign it. Also I don't think it is a good idea since it gets called big time before, during and after the execution.
I usually prefer to solve this problem by having a boolean property in my view model (e.g. HasMessageForProgressRing or IsProgressRingVisible). It's usually a more general-purpose solution. Then you can use a BooleanToVisibilityConverter.
If you truly want to implement a converter, just create a class that implements IValueConverter. The Convert implementation of this should be a piece of cake for your simple use case. ConvertBack isn't necessary in most cases (and won't be in yours). It would look something like this:
public class NullToCollapsed : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<GroupBox x:Name="groupBox" Header="Operating System" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="74" Width="280">
<StackPanel>
<RadioButton GroupName="Os" Content="Windows 7 (64-bit)" IsChecked="True"/>
<RadioButton GroupName="Os" Content="Windows 7 (32-bit)" />
</StackPanel>
</GroupBox>
I have several radio button groups in my application
How can I access which one has been checked in the Code-Behind using C#?
Is it absolutely necessary to use x:Name= on each RadioButton or is there a better way?
Code samples always appreciated
Yes! There is a better way, its called binding. Outside of binding, you are pretty much stuck (I can imagine handling all the checked events separately, and assigning to an enum, but is that really better?)
For radio buttons, you would typically use an enum to represent all the possible values:
public enum OsTypes
{
Windows7_32,
Windows7_64
}
And then bind each of your radio buttons to a global "selected" property on your VM. You need a ValueEqualsConverter for this:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((bool)value) ? parameter : Binding.DoNothing;
}
And then your radio buttons look like:
<RadioButton Content="Windows 7 32-bit"
IsChecked= "{Binding CurrentOs,
Converter={StaticResource ValueEqualsConverter},
ConverterParameter={x:Static local:OsTypes.Windows7_32}}"
Of course, you have a property in your VM:
public OsTypes CurrentOs {get; set;}
No x:Name, complicated switch statements, or anything else. Nice, clean, and well designed. MVVM works with WPF, use it!
I have an ObservableCollection in my Model, displayed in a ListBox in my View. Each ListBoxItem displays a radio button which should allow the user to choose one of the items. A property of the ViewModel should record this choice by holding a reference to the chosen item.
How can I set up a two-way binding to do this?
An IValueConverter should allow me to bind RadioButton.IsChecked to the VM property, with the actual item in which the radio button occurs passed to the converter either as a parameter or a value in an IMultiValueConverter. This way I can:
Return true / false for Convert() based on comparison of the VM property and the item the radio button is associated with.
Return the item if IsChecked==true and Binding.DoNothing otherwise for ConvertBack().
However, ValueConverter parameters cannot be bound to because they aren’t dependency properties (so I can't bind to the item to use as a ValueConverter parameter) and I cannot use the MultiConverter because although Convert() will receive both values of interest, ConvertBack() will only receive the value of IsChecked.
Notes
The built in ListBox selection mechanism is already in use for other purposes.
The collection of interest is nested in another collection and presented in a containing ListBox.
The ListBox is bound to a collection in the Model. I am hoping not to implement events in the Model collections that tell the VM how to record events in the View, for obvious reasons.
I managed to solve this:
Include a converter on the resources node of the ItemsPanel which wraps each ListBoxItem (or somewhere else within the repeated part of the tree), so there is one converter instance per item.
Add a property (Host in my example) to the converter for the item each converter attaches to and initialise it somehow* as each ListBoxItem is created.
Bind IsChecked to the ViewModel property you need to have populated using the converter.
*I tried the Host property as a DP with binding to the DataContext (which should be one member of the collection the ListBox is bound to at the point at which the converter is located), but I simply could not get this to work for my nested ListBoxes. I resorted to the Initialized event of the containing element and some code behind (which did allow Host to revert to an ordinary property; the DP was overkill but had been necessary for binding).
The DataTemplate:
<StackPanel Initialized="StackPanel_Initialized">
<StackPanel.Resources>
<!-- ExlcusionRadioConverter.Host is initialised in code behind -->
<local:ExclusionRadioConverter x:Key="ExclusionRadio" />
</StackPanel.Resources>
<RadioButton
GroupName="Exclusion"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}, Mode=FindAncestor}, Converter={StaticResource ExclusionRadio}, Path=DataContext.ExclusionCriterion}" />
<Label Content="{Binding Description}" />
</StackPanel>
The Panel Initialized event (NB this answer originally had this code in the Loaded event handler, but this was causing some problems):
private void StackPanel_Initialized(object sender, EventArgs e)
{
StackPanel panel = (StackPanel)sender;
ExclusionRadioConverter converter = (ExclusionRadioConverter)panel.FindResource("ExclusionRadio");
converter.Host = panel.DataContext as OptionListMember;
}
The Converter:
[ValueConversion(typeof(object), typeof(bool))]
public class ExclusionRadioConverter : IValueConverter
{
public OptionListMember Host { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ReferenceEquals(value, Host);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? Host : Binding.DoNothing;
}
}
First off, let me state I am an amateur when it comes to wpf. I am trying to create an a collapsing/expanding type action for a wpf button, meaning when a user clicks a button, I would like the button selected to expand a new list of buttons beneath it. This is meant to be the navigation type for the web-enabled application. I would also like to create a collapsing interaction when the button is pressed again on an opened list.
There is a default control for this in WPF, named the Expander. If you want to change the appearance or the animations you could look into templating and styling of WPF. By default this control should meet most of your requirements.
My first idea is to use a ToggleButton and bind its IsChecked property to a the visibility of the element you want to show. You would need a converter then to convert the boolean Checked value to a Visibility value. Here is an example:
<Grid>
<Grid.Resources>
<BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Grid.Ressources>
<ToggleButton x:Name="toggleButton" Content="Toggle" />
<Grid Visibility={Binding IsChecked, ElementName=toggleButton, Converter={StaticResource BoolToVisibilityConverter}}>
<!-- place your content here -->
</Grid>
</Grid>
The converter is a class implementig IValueConverter:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool i = value is bool ? (bool) value : false;
return i ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Just use a ToggleButton and bind the visibility of the section to its IsChecked state as normally done in the Expander control (which you of course could just use instead).