Preventing almost duplicate RelayCommands in MVVM/MDI app - c#

I'm using an MDI solution (see http://wpfmdi.codeplex.com/) and MVVM.
I use one RelayCommand to bind the toolbar and/or menu to the Main ViewModel, like:
ICommand _editSelectedItemCommand;
public ICommand EditSelectedItemCommand
{
get
{
return _editSelectedItemCommand ?? (_editSelectedItemCommand = new RelayCommand(param => CurrentChildViewModel.EditSelectedItem(),
param => ((CurrentChildViewModel != null) && (CurrentChildViewModel.CanExecuteEditSelectedItem))));
}
}
However, in the child window, to bind a button to the same functionality I need another RelayCommand which is almost equal except it calls the methods EditSelectedItem and CanExecuteEditSelectedItem directly. It would look like:
ICommand _editSelectedItemCommand;
public ICommand EditSelectedItemCommand
{
get
{
return _editSelectedItemCommand ?? (_editSelectedItemCommand = new RelayCommand(param => EditSelectedItem(),
param => CanExecuteEditSelectedItem))));
}
}
I need about 10 and in the future maybe 50 or more of such commands so I like to do it the right way now.
Is there a way to prevent this or a better way to do this?

You can remove the first command from the main viewmodel, because the command in the child viewmodel is more than enough.
Just use the binding like this in the xaml markup:
<Button Command="{Binding CurrentChildViewModel.EditSelectedItemCommand}"
Content="Button for the main view model" />
Also if I understand your code correctly, it has the stipulation that if the CurrentChildViewModel property is null than the command will be disabled.
If you need such behavior, you should add this converter to your code and slightly rewrite the binding:
public class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Application.Resources>
<local:NullToBooleanConverter x:Key="NullToBooleanConverter" />
</Application.Resources>
<!-- your control -->
<Button Command="{Binding CurrentChildViewModel.EditSelectedItemCommand}"
IsEnabled="{Binding CurrentChildViewModel, Converter={StaticResource NullToBooleanConverter}}" />

Related

ValueConverter Convert Methods are not invoked in WPF

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}}"/>

New window as CommandParameter every time

I want a button to show app settings window like this:
<Window.Resources>
<local:SettingsWindow x:Key="SettingsWnd"/>
</Window.Resources>
<Window.DataContext>
<local:MyViewModel/>
</Window.DataContext>
<Button Command="{Binding ShowSettingsCommand}"
CommandParameter="{DynamicResource SettingsWnd}"/>
The ViewModel kinda thing:
class MyViewModel : BindableBase
{
public MyViewModel()
{
ShowSettingsCommand = new DelegateCommand<Window>(
w => w.ShowDialog());
}
public ICommand ShowSettingsCommand
{
get;
private set;
}
}
The problem is that it only works one time because you can't reopen previously closed windows. And apparently, the XAML above does not make new instances to open like that.
Is there a way to pass new window as CommandParameter every time the command is called?
Does this converter solve your problem?
class InstanceFactoryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var type = value.GetType();
return Activator.CreateInstance(type);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
...
<Window.Resources>
<local:SettingsWindow x:Key="SettingsWnd"/>
<local:InstanceFactoryConverter x:Key="InstanceFactoryConverter"/>
</Window.Resources>
...
<Button Command="{Binding ShowSettingsCommand}"
CommandParameter="{Binding Source={StaticResource SettingsWnd}, Converter={StaticResource InstanceFactoryConverter}}"/>

How to pass data between pages on navigation using mvvvm in wp 8.1?

I am new to windows phone 8.1. I have a view List and when user click on an item he should move to the item details pages for that item as a result inside view model I bind a command that perform the following :
((Frame)Window.Current.Content).Navigate(typeof(ItemView), item);
Till now every thing is working fine. But how can I receive the item object in the view model of item details page?
I can access it in the code behind but what is the mvvm best practices for that problem.
Let's assume you have the following list. I left out the declaration of the item template and the itemsource:
<ListView IsItemClickEnabled="True" x:Name="lvItems">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ItemClick">
<Core:InvokeCommandAction Command="{Binding NavigateDetailsCommand, Mode=OneWay}" InputConverter="{StaticResource ItemClickConverter}" CommandParameter="{Binding SelectedItem, ElementName=lvItems}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ListView>
Then you'll need a converter that converts the click event to the actual item:
public class ItemClickConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var icea = (ItemClickEventArgs)value;
if (icea != null)
return icea.ClickedItem;
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The last thing is the viewmodel where you're passing everything to a command:
public RelayCommand<MyEntity> NavigateDetailsCommand;
// In your constructor, do this:
this.NavigateDetailsCommand= new RelayCommand<MyEntity>(navigateDetails);
// I'm assuming you injected a navigation service into your viewmodel..
this._navigationService.Configure("itemviewpage", typeof(ItemView));
// Declare a method that does the navigation:
private void navigateDetails(MyEntity item) {
this._navigationService.NavigateTo("itemviewpage", item);
}

Different Iitems in GridView

I'm trying to implement the following "start screen" interface for my Windows Store App.
I've figured a Gridview would be the component to use.
How do i display different type of items in a GridView?
Is this a good approach:
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<ContentControl Content="{Binding Converter={StaticResource local:ContentTypeToControlConverter}}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
And Class
public class ContentTypeToControlConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
if (value is MenuItem)
{
return new MenuItemControl();
}
else if (value is RecentViewItem)
{
return new RecentItemControl();
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
If you are targeting Windows 8.1 or higher - you could use the Hub control. That way you can avoid having to specify groups of items for your GridView, but implementing a DataTemplateSelector and setting it as a ItemTemplateSelector property of the GridView is the way to have items based on different templates.

WPF grid IsEnabled using ValueConverter

I have a WPF window with a Grid and a TreeView. The datacontext for the Grid is bound to the selected item on the treeview. However, because not all treeviewitems are applicable, I want to disable the grid if the treviewitem isn't applicable. So, I created a value converter to do a null check and return a bool. (Applicable items would not be null in this case)
The problem is that the value converter is never used. I set break points and they are never hit. I have other value converters I'm using and they all work just fine.
Is there something I'm missing?
<Grid Grid.Column="1" Grid.Row="0" DataContext="{Binding MyVal}" IsEnabled="{Binding MyVal, Converter={StaticResource NullCheckConverter}}" Margin="2,2,2,2">
Not that it's important for this question but here is the ValueConverter code:
internal class NullCheckValueConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(value == null);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
That's because you bind DataContext to the same value as you binding IsEnabled. So for IsEnabled it actually looking for MyVal.MyVal. Replace to:
IsEnabled="{Binding Converter={StaticResource NullCheckConverter}}"
Also further if you have issues with binding, check in debug mode output window for binding errors.

Categories

Resources