WPF PRISM MVVM get selected item from grid control - c#

I have a DevExpress Grid Control. I want to enable/disable a button based on the selected rows in the grid control, i.e., if any rows are selected in the grid control then this button should be enabled. Following is my GridControl code:
<dxg:GridControl x:Name="gridFloorplans" Grid.Column="1" Grid.Row="1" AutoGenerateColumns="None"
ItemsSource="{Binding FloorplanList.Result.View}"
SelectedItems="{Binding SelectedFloorplan,Mode=TwoWay}"
dx:ThemeManager.Theme="Default" SelectionMode="Row">
<dxg:GridControl.View>
<dxg:TableView AllowGrouping="False" ShowGroupPanel="False" AllowEditing="False" ShowDataNavigator="True" DataNavigatorButtons="Navigation" />
</dxg:GridControl.View>
<dxg:GridControl.Columns>
<dxg:GridColumn FieldName="Name" Header="Floorplan Name" Fixed="Left" />
<dxg:GridColumn FieldName="Season" Fixed="Left" />
<dxg:GridColumn FieldName="Version" Fixed="Left" />
</dxg:GridControl.Columns>
</dxg:GridControl>
Following is my ViewModel code:
private ObservableCollection<FloorplanData> _selectedFloorplan;
public FloorplanSearchViewModel(IErrorHandlerService inErrorHandler, INavigationService inNavigationService,
ISpaDataAdapter inDataAdapter, IAuthorizationService inAuthService)
{
// Set the commands
this.ShowStoreSetCommand = new DelegateCommand<IList<object>>(this.ShowStoreSet, this.CanShowStoreSet);
this.SearchFloorplansCommand = new DelegateCommand(this.SearchFloorplans);
this.ShowStatusChangeCommand = new DelegateCommand<IList<object>>(this.ShowStatusChange, this.CanShowStatusChange);
// Set up the default values for the search
this.StatusList = new List<object>();
this.StatusList.Add(Enum.GetName(typeof(FloorplanData.FloorplanStatus), FloorplanData.FloorplanStatus.Pending));
this.StatusList.Add(Enum.GetName(typeof(FloorplanData.FloorplanStatus), FloorplanData.FloorplanStatus.Review));
//Initiate the SelectedFloorplan property
//SelectedFloorplan = new ObservableCollection<FloorplanData>();
}
public ObservableCollection<FloorplanData> SelectedFloorplan
{
get
{
return _selectedFloorplan;
}
set
{
_selectedFloorplan = value;
this.ShowStatusChangeCommand.RaiseCanExecuteChanged();
}
}
public NotifyTaskCompletion<CollectionViewSource> FloorplanList
{
get;
private set;
}
private void ShowStatusChange(IList<object> inFloorplans)
{
try
{
// Create the navigation output
NavigationParameters args = new NavigationParameters();
args.Add(FloorplanStatusChangeViewModel.PARAM_FLOORPLAN_ID_LIST, GetFloorplanIdList(inFloorplans));
_navigationService.NavigateTo<Views.FloorplanStatusChangeView>(args);
}
catch (Exception ex)
{
_errorHandler.HandleError(ex);
}
}
private bool CanShowStatusChange(IList<object> inFloorplans)
{
// Check security to see if the current user is allowed to enter the status change screen
if (_authService.GetAccessLevel(1470) > AuthorizationLevel.None)
{
if (SelectedFloorplan!=null)
return true;
else
return false;
}
else
{
return false;
}
}
Following is the xaml code for the button:
<Button Margin="4,2" Content="Status Change" Command="{Binding ShowStatusChangeCommand}"
CommandParameter="{Binding SelectedItems, ElementName=gridFloorplans}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="ToolTip" Value="Open the Floorplan Status Change view for the selected floorplans" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="ToolTip" Value="You do not have access to open the Floorplan Status Change view" />
</Trigger>
<DataTrigger
Binding ="{Binding ElementName=gridFloorplans, Path=SelectedFloorplan}"
Value="-1">
<Setter Property="Button.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
How can I enable/disable ShowStatus button based on whether any row is selected in the grid or not?

//You are not using `inFloorplans` parameter within your method body
//Need not pass this parameter
private bool CanShowStatusChange(IList<object> inFloorplans)
Same as CanShowStatusChange method create a property and bind it to the Button which you want to enable/disable
public bool CanShowStatusChange
{
get
{
if (_authService.GetAccessLevel(1470) > AuthorizationLevel.None)
{
if (SelectedFloorplan!=null)
return true;
else
return false;
}
else
{
return false;
}
}
}
Selected floor plan cant be an observable collection. The name detotes its a It denotes a single object. So
private FloorplanData _selectedFloorplan;
public FloorplanData SelectedFloorplan
{
get
{
return _selectedFloorplan;
}
set
{
_selectedFloorplan = value;
NotifyPropertyChanged("SelectedFloorplan");
//or its equivalent method to notify the change
NotifyPropertyChanged("CanShowStatusChange");
//or its equivalent method to notify the change of CanShowStatusChange.
}
}
Make sure you bind SelectedFloorplan property and CanShowStatusChange property in your UI so that they are updated.

1) IsEnabled will get bool value from CanShowStatusChange, so you dont need style.
2) You have grid`s selected items in VM, so why pass it via parameter?
<Button Margin="4,2" Content="Status Change" Command="{Binding ShowStatusChangeCommand}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="ToolTip" Value="Open the Floorplan Status Change view for the selected floorplans" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="ToolTip" Value="You do not have access to open the Floorplan Status Change view" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
3) You set SelectedFloorplan ones, then you just change items in collection! It means than we should subscribe on CollectionChanged
public FloorplanSearchViewModel(IErrorHandlerService inErrorHandler, INavigationService inNavigationService,
ISpaDataAdapter inDataAdapter, IAuthorizationService inAuthService)
{
// Set the commands
this.ShowStoreSetCommand = new DelegateCommand<IList<object>>(this.ShowStoreSet, this.CanShowStoreSet);
this.SearchFloorplansCommand = new DelegateCommand(this.SearchFloorplans);
this.ShowStatusChangeCommand = new DelegateCommand<IList<object>>(this.ShowStatusChange, this.CanShowStatusChange);
// Set up the default values for the search
this.StatusList = new List<object>();
this.StatusList.Add(Enum.GetName(typeof(FloorplanData.FloorplanStatus), FloorplanData.FloorplanStatus.Pending));
this.StatusList.Add(Enum.GetName(typeof(FloorplanData.FloorplanStatus), FloorplanData.FloorplanStatus.Review));
//Initiate the SelectedFloorplan property
SelectedFloorplan = new ObservableCollection<FloorplanData>();
SelectedFloorplan.CollectionChanged += SelectedFloorplanOnCollectionChanged;
}
private void SelectedFloorplanOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
this.ShowStatusChangeCommand.RaiseCanExecuteChanged();
}
public ObservableCollection<FloorplanData> SelectedFloorplan
{
get
{
return _selectedFloorplan;
}
set
{
_selectedFloorplan = value;
this.ShowStatusChangeCommand.RaiseCanExecuteChanged();
}
}
4) And
private bool CanShowStatusChange()
{
// Check security to see if the current user is allowed to enter the status change screen
if (_authService.GetAccessLevel(1470) > AuthorizationLevel.None)
{
if (SelectedFloorplan!=null && SelectedFloorplan.Any())
return true;
else
return false;
}
else
{
return false;
}
}

Related

WPF MVVM Datagrid Cell Validation Error Behavior Issue: Why is my value being removed?

My issue is the behavior of the validation errors in the DataGrid. It validates against my model object's property and displays the correct message, but the validation disappears, along with the original value whenever I select a different row.
In the example .gif below, I remove the name (backspace), hit enter (get the validation message), then click to a different row. Whenever the selected row changes I would expect either a) the validation error to remain OR b) the original value to return, but the row stays blank and the validation error is gone until I double click the row. Once I double click, the original value returns.
I would prefer the validation error to persist, but I'll take either at this point.
Here is the datagrid textblock style:
<Style x:Key="datagridElemStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Yellow" />
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Here is the actual DataGridTextColumn:
<DataGridTextColumn Header="NAME"
Width="300"
Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus,
ValidatesOnExceptions=True}"
ElementStyle="{StaticResource datagridElemStyle}"
CanUserReorder="False" />
This is my ViewModel wrapper object (irrelevant parts omitted):
public class PointVM : INotifyPropertyChanged
{
public Point DataContext { get; set; }
public string Name
{
get { return DataContext.Name; }
set
{
if (value != DataContext.Name)
{
DataContext.Name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
}
And finally, here is my Model (irrelevant parts omitted):
public abstract class Point
{
private string _name;
public string Name
{
get { return _name; }
set
{
string trimmedVal = value.Trim();
#region Validation
if (string.IsNullOrEmpty(trimmedVal))
throw new Exception("Name cannot be empty.");
if (Regex.IsMatch(trimmedVal, #"[^A-Za-z0-9\-_ ]$"))
throw new Exception("Invalid character in name.");
if (trimmedVal.Length > 64)
throw new Exception("Name is too long.");
if ((from p in PointList
where p.Name.Equals(trimmedVal, StringComparison.OrdinalIgnoreCase)
select p).Count() > 0)
throw new Exception("Name is already used.");
#endregion
_name = trimmedVal;
}
}
Thanks for your time.
DataGrid validations can be super annoying. When a DataGridTextColumn begins editing, the actual value of the binding is restored, which is why you see a revert when you enter edit mode.
You basically need to stop that call by handling the BeginningEdit event for the DataGrid.
<DataGrid AutoGenerateColumns="False" BeginningEdit="dg_BeginningEdit">
Code-Behind
private void dg_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
e.EditingEventArgs.Handled = true;
}
FYI: Your binding updates on LostFocus. This solution may not work well if you update on PropertyChanged in the future as it changes the order of operation.

Image Opacity, turn of and on

I have five images, when you click one of them I want that one to get full opacity while the other only gets half, to show it is the selected one.
I am using MVVM and generally wondering if I'm doing it the right way
I was thinking about passing the name of the imagesource binded into a property.
<StackLayout Grid.Row="3" Grid.Column="1" Orientation="Horizontal" Spacing="0">
<Image Source="{Binding StatusUnresolved}" HorizontalOptions="Center"
VerticalOptions="Center" HeightRequest="40" Opacity="{Binding StatusUnresolvedOpacity}">
<Image.GestureRecognizers>
<!--<TapGestureRecognizer Command="{Binding Source={x:Reference this}, Path=OnStatusTappedCommand}" CommandParameter="{Binding StatusUnresolved}" />-->
</Image.GestureRecognizers>
</Image>
</StackLayout>
The list that turns the string into status later on.
public List<IssueStatusModel> PossibleStatusValues
{
get
{
var items = new List<IssueStatusModel>
{
new IssueStatusModel("statusUnresolved.png", IssueStatus.Unresolved),
new IssueStatusModel("statusInProgress.png", IssueStatus.InProgress),
new IssueStatusModel("statusDone.png", IssueStatus.Done)
};
return items;
}
}
Property for opacity
public double StatusDoneOpacity
{
get { return statusDoneOpacity; }
set
{
if (statusDoneOpacity != value)
{
statusDoneOpacity = value;
NotifyPropertyChanged(nameof(StatusUnresolvedOpacity));
}
}
}
public string StatusDone
{
get { return "statusDone.png"; }
}
public void OnStatusTapped(string fileName)
{
foreach (IssueStatusModel item in StatusValues)
{
if (item.Name != fileName) continue;
Issue.Status = item.Status;
StatusChecker();
return;
}
}
}
Switch statement Changing all the opacities.
private void StatusChecker()
{
switch (Issue.Status)
{
case IssueStatus.Unresolved:
StatusUnresolvedOpacity = 1;
StatusInProgressOpacity = 0.5;
StatusDoneOpacity = 0.5;
StatusText = "Unresolved";
break;
case IssueStatus.InProgress:
StatusUnresolvedOpacity = 0.5;
StatusInProgressOpacity = 1;
StatusDoneOpacity = 0.5;
StatusText = "In Progress";
break;
case IssueStatus.Done:
StatusUnresolvedOpacity = 0.5;
StatusInProgressOpacity = 0.5;
statusDoneOpacity = 1;
StatusText = "Done";
break;
}
}
The way I'd attack this, if you have multiple images, create an ImageVm and encapsulate any image specific implementation details i.e. enum State and an IsSelected notification properties. Of course if you only have 1 image this becomes trivially easy and you don't need vms
Use a DataTrigger that binds to an IsSelected MVVM property to set the Opacity and state if you need to change the image source. Obviously on click you will need to set the IsSelected Property and deselect the other VMs
Example of DataTrigger for IsSelected
<Image Grid.Column="2" Stretch="None">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Opacity" Value="0.5" />
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding IsSelected}">
<Setter Property="Opacity" Value="0.5"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
Update
You CAN use triggers with enums, and you can use a tap recognizers to fire commands in your main viewmodals. also commands can take parameters as well.
It's probably better (knowing what you have described in the comments) to just make a State and Severity enum and bind to it, and set the State and Severity via a command by a gesture.
Then you could just make a Trigger for each Image to change the Opacity for each image on the various state and severity.

WPF checkbox all not not updating on one checkbox row is unchecked

I had a checkbox all column inside the datagrid in WPF C#.
<DataGridCheckBoxColumn Binding="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}" CanUserSort="False">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" HorizontalAlignment="Center" Margin="0,0,5,0" IsEnabled="{Binding Path=DataContext.IsCbxAllEnabled,RelativeSource={RelativeSource AncestorType=DataGrid}}"
IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid},UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
When I check the All checkbox, of course, it will mark all the checkboxes, but once I uncheck one checkbox, the All checkbox is still checked. This should be unchecked. How should I do that using WPF C#.
If I understood you correctly - after any change of IsSelected property inside collection item you should update AllSelected value.
So, you need some callback inside all your items(event or Action or any mechanism you want) and change get logic for AllSelected
Here is some draft for item IsSelected property and constructor:
public bool IsSelected {
get { return isSelected; }
set {
isSelected = value;
OnPropertyChanged();
if (globalUpdate != null) globalUpdate();
}
}
public ItemClass(Action globalUpdate, ...your parameters) {
this.globalUpdate = globalUpdate;
...do smth with your parameters
}
Example of usage:
new ItemClass(() => OnPropertyChanged("AllSelected"))
And of course don't forget about AllSelected getter
public bool AllSelected {
get { return YourGridItemsCollection.All(item => item.IsSelected); }
Now when you check manually all items then AllSelected will be automatically checked, and unchecked when you uncheck any item.

WPF Dependency Property Issue

I am wondering if anyone could explain me the difference between
binding a selected value of a Collection to a comboBox.
Or Binding the value to a Button Content.
Like that
<ComboBox x:Name="_culturedTitleViewModelSelector" Visibility="Hidden" Style="{StaticResource ResourceKey=_culturedTitleViewModelSelectorStyle}"
ItemsSource="{Binding Path=AvailableCultures, Source={x:Static Localized:ResourcesManager.Current}}"
SelectedValue="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
<Button x:Name="LanguageBtn" Content="{Binding Path=CurrentCulture, Source={x:StaticLocalized:ResourcesManager.Current}}"
The issue is If i Don't use the ComboBox up there, the DependencyProperty I Have in another class is not being called.
But if I Use the comboBox everything works...
Altought the comboBox doesnt do anything it's just a "workarround"
In my CS code when i CLick on my button I DO that :
ResourcesManager.Current.SwitchToNextCulture();
//We use a dummy comboBox to make sure the LanguageBehavior Property is being notified.
_culturedTitleViewModelSelector.SelectedItem = ResourcesManager.Current.CurrentCulture;
And if I Dont set the SelectedItem of the combobox to another culture. My languageBehavior class is not notified.
:
public class LanguageBehavior
{
public static DependencyProperty LanguageProperty =
DependencyProperty.RegisterAttached("Language",
typeof(string),
typeof(LanguageBehavior),
new UIPropertyMetadata(OnLanguageChanged));
public static void SetLanguage(FrameworkElement target, string value)
{
target.SetValue(LanguageProperty, value);
}
public static string GetLanguage(FrameworkElement target)
{
return (string)target.GetValue(LanguageProperty);
}
private static void OnLanguageChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as FrameworkElement;
if (e.NewValue!=null)
element.Language = XmlLanguage.GetLanguage(e.NewValue.ToString());
}
}
I'd expect ComboBox Content to work the same as Button Content.
In my Generic.Xaml i do that :
<Style TargetType="{x:Type TextBlock}" x:Key="_textBlockLanguageProperty">
<Setter Property="WpfServices:LanguageBehavior.Language" Value="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
/>
</Style>
And that is CurrentCulture
public CultureInfo CurrentCulture
{
get { return CultureProvider.Current; }
set
{
if (value != CultureProvider.Current)
{
CultureProvider.Current = value;
OnCultureChanged();
}
}
}
Current :
public static ResourcesManager Current
{
get
{
if (_resourcesManager == null)
{
var cultureProvider = new BaseCultureProvider();
_resourcesManager = new ResourcesManager(cultureProvider);
_resourcesManager.Init();
}
return _resourcesManager;
}
}
EDIT :
My _culturedTitelViewModelSelectorStyle is
<Style TargetType="{x:Type ComboBox}" x:Key="_culturedTitleViewModelSelectorStyle">
<Setter Property="DisplayMemberPath" Value="DisplayName" />
<Setter Property="SelectedValuePath" Value="." />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Margin" Value="5" />
<Setter Property="SelectedIndex" Value="0" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
In the ComboBox you are binding the SelectedValue to a specific culture. This will select that culture from the list of available cultures, and therefor, trigger a set on the CurrentCulture property.
The Content property of a Button is merely displaying something to the user, it is not doing any assigning. It reads the property value and then displays it. That is why you need to manually change the Culture in the Click event to get it to do anything.
If you want the user to be able to select a value from a list of available values, a ComboBox or ListBox is the way to go. A Button is for triggering a specific action, not for selecting from a list.

Make select controls visible based on selected tab using xaml

I've got following code:
private Dictionary<int, UserControl> tabControls = new Dictionary<int, UserControl>();
public MainWindow()
{
InitializeComponent();
tabControls[0] = new Panel1();
tabControls[1] = new Panel2();
tabControls[2] = new Panel3();
tabControls[3] = new Panel4();
tabControls[4] = new Panel5();
tabControls[5] = new Panel6();
tabControls[6] = new Panel7();
tabControls[7] = new Panel8();
}
public object SelectedTab
{
//this is assigned from xaml binding
set
{
OnCurrentTabChanged(tabControl.SelectedIndex);
}
}
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
}
Every time the user selects different tab, an other control appears.
Is there any way to simplify this using xaml?
I cannot put the controls themselves inside the tab control
I've done this before with another TabControl which has it's headers and frame hidden. Then I just bind the SelectedIndex to your other tab's SelectedIndex, and the two are synchronized
<!-- TabControl without the TabHeaders -->
<Style x:Key="TabControl_NoHeadersStyle" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<!-- This is needed to draw TabControls with Bound items -->
<StackPanel IsItemsHost="True" Height="0" Width="0" />
<ContentPresenter x:Name="PART_SelectedContentHost"
ContentSource="SelectedContent" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can setup your two tab controls, each bound to different sources, and bind the SelectedIndex of one to the SelectedIndex of the other
<TabControl x:Name="MainTabControl" />
<TabControl ItemsSource="{Binding Panels}"
SelectedIndex="{Binding ElementName=MainTabControl, Path=SelectedIndex}"
Style="{StaticResource TabControl_NoHeadersStyle}" />
Another alternative is to bind the SelectedIndex to something in your code-behind, then anytime it changes, raise a PropertyChanged notification on another property that exposes the panel you want to display.
<TabControl SelectedIndex="{Binding SelectedTabIndex} />
<ContentControl Content="{Binding SelectedPanel}" />
and in the code behind
public int SelectedTabIndex
{
get { return _selectedTabIndex;}
set
{
if (_selectedTabIndex != value)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
RaisePropertyChanged("SelectedPanel");
}
}
}
public UserControl SelectedPanel
{
get { return tabControls[SelectedTabIndex]; }
}
TabItem has an IsSelected propery you could bind to that I think would simplify the syntax.
public bool TabIsSelected
{
get { return tabIsSelected; }
set
{
if (value && dataDisplay != null)
{
dataDisplay.Children.Clear();
dataDisplay.Children.Add(tabControls[tabIndex]);
}
tabIsSelected = value;
}
But I still don't get why you can't just put the control in the tabitem?
using codebehind
void OnCurrentTabChanged(int tabIndex)
{
if (dataDisplay != null)
{
UIElemnt[] pp = dataDisplay.Children.Cast<UIElement>().ToArray();
Array.ForEach(pp, x=> x.visibility = Visibility.Collapsed);
pp[tabIndex].visibility = Visibility.Visible;
}
}

Categories