My project consists of a Treeview with a Datagrid as a child. The parent in the Treeview has a checkbox, and every row in the datagrid also has a check box. My goal is to bind the checkboxes together, so that when the parent checkbox is checked, all of the datagrid rows are checked as well. When the datagrid rows are checked I would like the parent checkbox to be updated based of the datagrid row checked values.
Parent checkbox IsChecked = true when all of the datagrid row checkboxes are true, null if only some are, and false if no rows are checked.
The problem I'm facing is that my dependency properties aren't updating and the PropertyChangedCallback is not getting called.
This is my Checkbox_Helper class in which the IsChecked properties of both the parent and the datagrid row checkboxes are bound to.
public class CheckBox_Helper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CheckBox_Helper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Parent && ((bool?)e.NewValue).HasValue)
{
foreach (Child s in (d as Parent).SheetList[0].SheetChildList)
{
if (s.IsSizeCompatible)
{
SetIsChecked(s, (bool?)e.NewValue);
}
}
}
if (d is Child)
{
AddToCheckedElements(d as Child);
int _checked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == true).Count();
int _unchecked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == false).Count();
int _total = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Count();
if ((d as Child).MegaParent.SelectedRowList != null && (d as Child).MegaParent.SelectedRowList.Count > 0)
{
(d as Child).MegaParent.SelectedRowList.Where(i => GetIsChecked(i) != GetIsChecked(d as Child)).ToList().ForEach(i => CheckBox_Helper.SetIsChecked(i, GetIsChecked(d as Child)));
}
if (_checked == 0)
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = false;
}
else
{
(d.GetValue(ParentProperty) as Parent).HasCheckedItems = true;
}
(d.GetValue(ParentProperty) as Parent).CheckedRatio = $"{_checked}/{_total}";
if (_unchecked > 0 && _checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, null);
return;
}
if (_checked > 0)
{
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, true);
return;
}
CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, false);
}
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(Parent), typeof(CheckBox_Helper));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CheckBox_Helper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CheckBox_Helper.IsCheckedProperty);
}
// Set and Get Parent are unused since they are hardcoded categories
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CheckBox_Helper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CheckBox_Helper.ParentProperty);
}
private static void AddToCheckedElements(Child child)
{
ObservableCollection<Child> tempList = child.MegaParent.CheckedElements;
if (!child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == true)
{
tempList.Add(child);
child.MegaParent.CheckedElements = tempList;
}
else if (child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == false)
{
tempList.Remove(child);
child.MegaParent.CheckedElements = tempList;
}
}
}
This is the code for the Parent class:
public class Parent : DependencyObject
{
public string Name { get; set; }
public ObservableCollection<ChildList> SheetList { get; set; }
public static readonly DependencyProperty HasCheckedItemsProperty =
DependencyProperty.Register("HasCheckedItems", typeof(bool), typeof(Parent), new UIPropertyMetadata(false));
public bool HasCheckedItems
{
get { return (bool)GetValue(HasCheckedItemsProperty); }
set { SetValue(HasCheckedItemsProperty, value); }
}
public static readonly DependencyProperty CheckedRatioProperty =
DependencyProperty.Register("CheckedRatio", typeof(string), typeof(Parent), new UIPropertyMetadata($"0/0"));
public string CheckedRatio
{
get { return (string)GetValue(CheckedRatioProperty); }
set { SetValue(CheckedRatioProperty, value); }
}
public Parent(string name)
{
Name = name;
SheetList = new ObservableCollection<ChildList>();
}
}
This is the code for the Child class:
(Note: The MegaParent property is the viewmodel)
public class Child : DependencyObject
{
public ViewSheet Sheet { get; set; }
public string Name { get; set; }
public string Number { get; set; }
private string _paperSize = "";
public string PaperSize
{
get { return _paperSize; }
set { _paperSize = value; }
}
public static readonly DependencyProperty IsSizeCompatibleProperty =
DependencyProperty.Register("IsSizeCompatible", typeof(bool), typeof(Child), new UIPropertyMetadata(true));
public bool IsSizeCompatible
{
get { return (bool)GetValue(IsSizeCompatibleProperty); }
set { SetValue(IsSizeCompatibleProperty, value); }
}
public PrintSheets_ViewModel MegaParent { get; set; }
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(Child), new UIPropertyMetadata(false));
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); AddToSelectedList(); }
}
private void AddToSelectedList()
{
ObservableCollection<Child> tempList = MegaParent.SelectedRowList;
if (!MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == true)
{
tempList.Add(this);
MegaParent.SelectedRowList = tempList;
}
else if (MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == false)
{
tempList.Remove(this);
MegaParent.SelectedRowList = tempList;
}
}
public Child(string name, string number, ViewSheet sheet, string paperSize, bool isSizeCompatible, PrintSheets_ViewModel megaParent)
{
Name = name;
Number = number;
Sheet = sheet;
PaperSize = paperSize;
IsSizeCompatible = isSizeCompatible;
MegaParent = megaParent;
}
}
This is the XAML code for the Treeview:
<Border Grid.Row="1" Grid.RowSpan="4" Grid.Column="3" Grid.ColumnSpan="3" Margin="10,10,10,10"
BorderBrush="Black" BorderThickness="1">
<ScrollViewer x:Name="treeScroller" PreviewMouseWheel="treeScroller_PreviewMouseWheel" VerticalScrollBarVisibility="Auto">
<TreeView Background="LightGray" ItemsSource="{Binding tv_model.ParentItems}" x:Name="sheetTree"
ScrollViewer.CanContentScroll="False">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:Parent}" ItemsSource="{Binding SheetList}">
<Border BorderBrush="DarkGray" CornerRadius="5" Padding="5,0,0,0" Height="40"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusFortyFiveConverter}}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="Background" Value="#b5ffc0"/>
<Setter Property="BorderThickness" Value="3"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="Background" Value="LightCyan"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Cursor="Hand"/>
<Label Grid.Column="1" Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="SemiBold"/>
<Label Grid.Column="3" VerticalAlignment="Center" HorizontalContentAlignment="Right"
FontWeight="Normal" FontSize="12"
Content="Sheets Checked:"/>
<Label Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Right"
Content="{Binding CheckedRatio}">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="12"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Grid>
</Border>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type m:ChildList}">
<DataGrid x:Name="ChildDataGrid" ItemsSource="{Binding SheetChildList}"
Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusEightyConverter}}"
IsReadOnly="True" AutoGenerateColumns="False" SelectionMode="Extended" CanUserResizeColumns="True"
EnableRowVirtualization="True" CanUserAddRows="False" CanUserDeleteRows="False"
CanUserResizeRows="True" CanUserReorderColumns="False" CanUserSortColumns="True" >
<DataGrid.Resources>
<Style TargetType="DataGrid">
<EventSetter Event="LostFocus" Handler="OnLostFocus_DataGrid"/>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="20">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Cursor="Hand" IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Sheet Number" Binding="{Binding Number}" IsReadOnly="True" Width="Auto"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="Paper Size" Binding="{Binding PaperSize}" IsReadOnly="True" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Margin" Value="0,3,0,3"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ScrollViewer>
</Border>
I had the checkboxes working before I added some of the extra methods to the CheckBox_Helper, but I need those methods in order for other controls on my UI to function properly.
I feel like it could be a DataContext issue. I've tried adding RelativeSource to the IsChecked binding with the AncestorType as Datagrid, and I've also tried it as Window.
Still no luck, any help would be greatly appreciated.
Related
I have a problem in a WPF .NET Core 3.1 that I am writing.
There is a 'Main Page', where the user can input some filters that will be used to search for some files by an external webAPI; so far so good. The response from the webAPI is an XML with the list of the files available and the user must choose which of these files download. To do so, I have a 'popup box' where the user can read all the available files and selected the desired ones by checkboxes. I have to add some buttons to select / deselect all the files and here lies the problem: the files are selected but the fronted does not notice and keep showing them as unchecked.
In the main page, parsing the XML I generate a List of these objects:
public class righeNoteSpese {
public Boolean selezionato { get; set; }
public Boolean isOK { get; set; }
public String errore { get; set; }
// Other String fields...
public righeNoteSpese() {
selezionato = false;
isOK = true;
errore = String.Empty;
}
}
and I call the popup with
ListaXML l = new ListaXML(lr);
await this.ShowChildWindowAsync(l.listaXML);
where lr is the list of rows I found.
The code behind of the popup is
public partial class ListaXML : ChildWindow
{
public List<righeNoteSpese> Elenco = new List<righeNoteSpese>();
public ListaXML()
{
InitializeComponent();
}
public ListaXML(List<righeNoteSpese> listF) {
InitializeComponent();
this.DataContext = this;
Elenco = listF;
selFiles.ItemsSource = listF;
/* If not commented the foreach works and all the rows are checked!
foreach (righeNoteSpese r in Elenco)
{
if (r.isOK)
{
r.selezionato = true;
}
}*/
}
private void All_Click(object sender, RoutedEventArgs e)
{
foreach (righeNoteSpese r in Elenco) {
if (r.isOK)
{
r.selezionato = true;
}
}
}
}
The XAML of the popup is
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Name="Btn1" Width="100" Content="Select All" Grid.Row="0" Grid.Column="0" Margin="10 15 10 15" Click="All_Click" />
<DataGrid Name="selFiles" AutoGenerateColumns="False" CanUserAddRows="false" HorizontalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto" AlternatingRowBackground="LightGray" Grid.Row="1" Grid.ColumnSpan="4">
<DataGrid.Columns><DataGridTextColumn Header="Errore" Width="200" Binding="{Binding errore, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding isOK}" Value="False">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
<DataTrigger Binding="{Binding selezionato}" Value="True">
<Setter Property="Background" Value="SpringGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="SizeToHeader" IsReadOnly="True" Header="Select">
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding isOK}" Value="False">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
<DataTrigger Binding="{Binding selezionato}" Value="True">
<Setter Property="Background" Value="SpringGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding selezionato, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="2,0,2,0" IsEnabled="{Binding isOK, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Other columns... -->
</DataGrid.Columns>
</DataGrid>
</Grid>
If I check manually a checkbox everything works, the background changes and the change is passed back to the main page. If I use the button the values are changed and passed back to the main page but there is no change in the frontend but if I execute these same instructions when I call the page everything is OK.
What am I missing?
Thank you for your help.
I didn't implement correctly the INotifyPropertyChanged, I changed the class from
public Boolean selezionato { get; set; }
to
private Boolean _selezionato;
public Boolean selezionato {
get {
return _selezionato;
}
set {
_selezionato = value;
OnPropertyChanged("selezionato");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
and now it works fine.
I have DataGrid that bind a list
I have a method of ADD data and I want to let the user delete by chose the row with the mouse and use the delete button
The ADD method can add specific data that choose from Combobox and I don't want to let the user change the data directly by edit cell
I have try to set DataGrid.IsReadOnly = false and DataGridTextColumn.IsReadOnly = true
But the delete not work...
To add data I choose data by the Comboboxes and Click Button to execute method: AddNewData
It's doing ok..
How can I do it?
My Relevant DataGrid Named : x:Name="ShiftsGrid"
My Code Behind :
using Shifts.ModelView;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Shifts.Windows
{
/// <summary>
/// Interaction logic for ShiftsManager.xaml
/// </summary>
public partial class ShiftsManager : Window
{
public ShiftsManager()
{
InitializeComponent();
DataContext = new ShiftsManagerMV(Close);
Closing += ((ShiftsManagerMV)DataContext).ShiftsManager_Closing;
}
}
}
All my XAML code
<Window x:Class="Shifts.Windows.ShiftsManager"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Shifts.Windows"
mc:Ignorable="d"
Title="ShiftsManager" Height="450" Width="800">
<Grid>
<StackPanel Orientation="Horizontal">
<StackPanel Margin="10">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5 0 0 0">
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference PositionDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="תיאור עמדה" FontSize="15" />
<TextBox Text="{Binding PositionDescriptionColumnFilter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15" />
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference ShiftsTypeDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="סוג משמרת" FontSize="15"/>
<TextBox Text="{Binding ShiftsTypeDescriptionColumnFilter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15"/>
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference DaysTypeDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="יום המשמרת" FontSize="15"/>
<TextBox Text="{Binding DaysTypeDescriptionColumnFilter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15"/>
</StackPanel>
</Border>
</StackPanel>
<DataGrid CanUserDeleteRows="True" IsEnabled="true" FontSize="15" IsReadOnly="False"
x:Name="ShiftsGrid" ItemsSource="{Binding Shifts,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False"
BorderBrush="#FF1600F0" AlternatingRowBackground="SkyBlue" AlternationCount="2" SelectionMode="Single" Height="224"
SelectedItem="{Binding CurrentShifts,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FF001389"/>
<Setter Property="Foreground" Value="#CDCCFF"/>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding Id}" Value="-1">
<Setter Property="Background" Value="#FFFFAE00"/>
<Setter Property="Foreground" Value="#0051ff"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn MinWidth="150" IsReadOnly="True" x:Name="PositionDescriptionColumn" HeaderStringFormat="" Header="מספר עובד" Binding="{Binding Position.Description}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn MinWidth="150" IsReadOnly="True" x:Name="ShiftsTypeDescriptionColumn" HeaderStringFormat="" Header="מספר עובד" Binding="{Binding ShiftsType.Description}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn MinWidth="150" IsReadOnly="True" x:Name="DaysTypeDescriptionColumn" HeaderStringFormat="" Header="מספר עובד" Binding="{Binding DaysType.Description}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel >
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" >
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference PositionDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="תיאור עמדה" FontSize="15" />
<ComboBox DisplayMemberPath="{Binding Path=Description}" FontSize="15"
ItemsSource="{Binding Path=Positions}"
SelectedItem="{Binding Path=PositionsToAdd}">
</ComboBox>
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference ShiftsTypeDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="סוג משמרת" FontSize="15"/>
<ComboBox DisplayMemberPath="{Binding Path=Description}" FontSize="15"
ItemsSource="{Binding Path=ShiftsTypes}"
SelectedItem="{Binding Path=ShiftsTypesToAdd}">
</ComboBox>
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference DaysTypeDescriptionColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="יום המשמרת" FontSize="15" />
<ComboBox DisplayMemberPath="{Binding Path=Description}" FontSize="15"
ItemsSource="{Binding Path=DaysTypes}"
SelectedItem="{Binding Path=DaysTypesToAdd}">
</ComboBox>
</StackPanel>
</Border>
</StackPanel>
</StackPanel>
<Button Content="הוספה" FontSize="20" Command="{Binding AddNewData}" Background="Aquamarine"></Button>
<Button Content="עדכן את נתוני המשמרות" FontSize="25" Background="#FF4FCFFF" Command="{Binding UpdateAllShiftData}" Margin="20"></Button>
</StackPanel>
<StackPanel Margin="10">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="5 0 0 0">
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference WorkerNumberColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="מספר עובד" FontSize="15" />
<TextBox Text="{Binding WorkerNumberColumnFilter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15"/>
</StackPanel>
</Border>
<Border BorderThickness="1" BorderBrush="Black" Width="{Binding Source={x:Reference WorkerNameColumn}, Path=ActualWidth}">
<StackPanel>
<TextBlock TextAlignment="Center" Text="שם עובד" FontSize="15"/>
<TextBox Text="{Binding WorkerNameColumnFilter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="15"/>
</StackPanel>
</Border>
</StackPanel>
<DataGrid x:Name="WorkersGrid" ItemsSource="{Binding Workers,UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False"
BorderBrush="#FF1600F0" AlternatingRowBackground="SkyBlue" AlternationCount="2" SelectionMode="Single" Height="224" IsReadOnly="True"
SelectedItem="{Binding WorkerRowSelectedRowItem,UpdateSourceTrigger=PropertyChanged}" FontSize="15">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#FF001389"/>
<Setter Property="Foreground" Value="#CDCCFF"/>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected" Value="True">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Purple" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn MinWidth="150" x:Name="WorkerNumberColumn" HeaderStringFormat="" Header="מספר עובד" Binding="{Binding WorkerNumber}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn MinWidth="150" x:Name="WorkerNameColumn" Header="שם עובד" Binding="{Binding WorkerName}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</StackPanel>
</Grid>
</Window>
My Model code :
using IniFiles;
using MilBatDBModels.Shifts;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static DataBaseManager.ShiftsDBManager;
using System;
using System.Windows.Input;
using MilBatDBModels.Common;
using System.Windows;
using CommonWindows;
namespace Shifts.ModelView
{
public class ShiftsManagerMV : INotifyPropertyChanged
{
private Action close;
Dictionary<long, ObservableCollection<ShiftsExt>> TableList;
public ObservableCollection<ShiftsExt> Shifts { get; set; }
public ObservableCollection<Worker> Workers { get; set; }
public Positions PositionsToAdd { get; set; }
public ShiftsType ShiftsTypesToAdd { get; set; }
public DaysType DaysTypesToAdd { get; set; }
public ObservableCollection<Positions> Positions { get; set; }
public ObservableCollection<ShiftsType> ShiftsTypes { get; set; }
public ObservableCollection<DaysType> DaysTypes { get; set; }
public bool Changed;
private ShiftsExt currentShifts;
public ShiftsExt CurrentShifts {
get
{
return currentShifts;
}
set
{
currentShifts = value;
}
}
private Worker workerRowSelectedRowItem;
public Worker WorkerRowSelectedRowItem
{
get { return workerRowSelectedRowItem; }
set {
workerRowSelectedRowItem = value;
UpdateShifts();
}
}
public void ShiftsManager_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (Changed)
{
YesNoWindow yesNoWindow = new YesNoWindow("יש נתונים שלא עודכנו, האם לעדכן אותם בבסיס הנתונים?", "כן", "לא");
yesNoWindow.ShowDialog();
if (yesNoWindow.ResultYes)
{
UpdateAllShiftDataAction();
}
}
}
public ShiftsManagerMV(Action close)
{
Changed = false;
TableList = null;
ShiftsIniManager s = new ShiftsIniManager();
long CurrentShiftsClass = s.GetShiftsLocalClass();
Positions = new ObservableCollection<Positions> (ShiftsDBManagerInstance.GetPositions(CurrentShiftsClass));
ShiftsTypes = new ObservableCollection<ShiftsType>(ShiftsDBManagerInstance.GetShiftsTypes());
DaysTypes = new ObservableCollection<DaysType>(ShiftsDBManagerInstance.GetDaysTypes());
UpdateWorkers();
this.close = close;
}
private void UpdateShifts()
{
if (workerRowSelectedRowItem != null)
{
if (TableList[workerRowSelectedRowItem.ID] == null || TableList[workerRowSelectedRowItem.ID].Any() == false)
{
TableList[workerRowSelectedRowItem.ID] = new ObservableCollection<ShiftsExt>(ShiftsDBManagerInstance.SelectShifts(workerRowSelectedRowItem.ID));
}
Shifts = new ObservableCollection<ShiftsExt>(TableList[workerRowSelectedRowItem.ID].Select(se => se));
}
}
private void UpdateWorkers()
{
ShiftsIniManager s = new ShiftsIniManager();
long _class = s.GetShiftsLocalClass();
Workers = new ObservableCollection<Worker>
(ShiftsDBManagerInstance.SelectWorkers(_class));
if(TableList == null)
{
TableList = new Dictionary<long, ObservableCollection<ShiftsExt>>();
for (int i = 0; i < Workers.Count; i++)
{
TableList.Add(Workers[i].ID, new ObservableCollection<ShiftsExt>());
}
}
}
private string _WorkerNumberColumnFilter;
public string WorkerNumberColumnFilter
{
get
{
return _WorkerNumberColumnFilter;
}
set
{
if (_WorkerNumberColumnFilter != value)
{
_WorkerNumberColumnFilter = value;
RunFilterAction();
}
}
}
private string _WorkerNameColumnFilter;
public string WorkerNameColumnFilter
{
get
{
return _WorkerNameColumnFilter;
}
set
{
if (_WorkerNameColumnFilter != value)
{
_WorkerNameColumnFilter = value;
RunFilterAction();
}
}
}
private void RunFilterAction()
{
UpdateWorkers();
List<Worker> wTemp = Workers.ToList();
if (string.IsNullOrWhiteSpace(_WorkerNumberColumnFilter) == false)
{
wTemp = wTemp.
Where(w => w.WorkerNumber.ToLower().Contains(_WorkerNumberColumnFilter.ToLower())).ToList();
}
if (string.IsNullOrWhiteSpace(_WorkerNameColumnFilter) == false)
{
wTemp = wTemp.
Where(w => w.WorkerName.ToLower().Contains(_WorkerNameColumnFilter.ToLower())).ToList();
}
Workers = new ObservableCollection<Worker>(wTemp);
}
private string _PositionDescriptionColumnFilter;
public string PositionDescriptionColumnFilter
{
get
{
return _PositionDescriptionColumnFilter;
}
set
{
if (_PositionDescriptionColumnFilter != value)
{
_PositionDescriptionColumnFilter = value;
RunFilterShiftsAction();
}
}
}
private string _ShiftsTypeDescriptionColumnFilter;
public string ShiftsTypeDescriptionColumnFilter
{
get
{
return _ShiftsTypeDescriptionColumnFilter;
}
set
{
if (_ShiftsTypeDescriptionColumnFilter != value)
{
_ShiftsTypeDescriptionColumnFilter = value;
RunFilterShiftsAction();
}
}
}
private string _DaysTypeDescriptionColumnFilter;
public string DaysTypeDescriptionColumnFilter
{
get
{
return _DaysTypeDescriptionColumnFilter;
}
set
{
if (_DaysTypeDescriptionColumnFilter != value)
{
_DaysTypeDescriptionColumnFilter = value;
RunFilterShiftsAction();
}
}
}
private void RunFilterShiftsAction()
{
UpdateShifts();
List<ShiftsExt> sTemp = Shifts.ToList();
if (string.IsNullOrWhiteSpace(_PositionDescriptionColumnFilter) == false)
{
sTemp = sTemp.
Where(s => s.Position.Description.ToString().ToLower().Contains(_PositionDescriptionColumnFilter.ToLower())).ToList();
}
if (string.IsNullOrWhiteSpace(_ShiftsTypeDescriptionColumnFilter) == false)
{
sTemp = sTemp.
Where(s => s.ShiftsType.Description.ToLower().Contains(_ShiftsTypeDescriptionColumnFilter.ToLower())).ToList();
}
if (string.IsNullOrWhiteSpace(_DaysTypeDescriptionColumnFilter) == false)
{
sTemp = sTemp.
Where(s => s.DaysType.Description.ToLower().Contains(_DaysTypeDescriptionColumnFilter.ToLower())).ToList();
}
Shifts = new ObservableCollection<ShiftsExt>(sTemp);
}
private ICommand _AddNewData;
public ICommand AddNewData
{
get
{
if (_AddNewData == null)
{
_AddNewData = new RelayCommand((param) => AddNewDataAction());
}
return _AddNewData;
}
}
private void AddNewDataAction()
{
Changed = true;
if (Shifts.Where(se=>(se.DaysType.Code == DaysTypesToAdd.Code) && (se.ShiftsType.Code == ShiftsTypesToAdd.Code)).Any())
{
MessageBox.Show("לא ניתן לתת לאותו עובד את אותה משמרת באותו יום");
return;
}
Worker w = workerRowSelectedRowItem;
Shifts.Add(new ShiftsExt(-1,
new Worker(w.ID, w.WorkerNumber, w.WorkerName,w.Address,w.City,w.PhoneNumber,w.PhoneNumber2,w.Number,w.ClassCode),
new Positions(PositionsToAdd.Code, PositionsToAdd.Description, PositionsToAdd.ClassCode),
new ShiftsType(ShiftsTypesToAdd.Code, ShiftsTypesToAdd.Description),
new DaysType(DaysTypesToAdd.Code, DaysTypesToAdd.Description)));
TableList[workerRowSelectedRowItem.ID] = new ObservableCollection<ShiftsExt>(Shifts.Select(se => se));
//ShiftsIniManager s = new ShiftsIniManager();
//for (int i = 0; i < Workers.Count(); i++)
//{
// Workers[i].ClassCode = s.GetShiftsLocalClass();
//}
//List<Worker> wTemp = Workers.ToList();
//wTemp.RemoveAll(w => w.WorkerName == null);
//ShiftsDBManagerInstance.UpdateWorkers(wTemp);
//UpdateWorkers();
}
private ICommand _UpdateAllShiftData;
public ICommand UpdateAllShiftData
{
get
{
if (_UpdateAllShiftData == null)
{
_UpdateAllShiftData = new RelayCommand((param) => UpdateAllShiftDataAction());
}
return _UpdateAllShiftData;
}
}
private void UpdateAllShiftDataAction()
{
for (int i = 0; i < TableList.Count; i++)
{
if (TableList.ElementAt(i).Value.Any()) {
ShiftsDBManagerInstance.UpdateShifts(TableList.ElementAt(i).Value.ToList());
}
}
//ShiftsIniManager s = new ShiftsIniManager();
//for (int i = 0; i < Workers.Count(); i++)
//{
// Workers[i].ClassCode = s.GetShiftsLocalClass();
//}
//List<Worker> wTemp = Workers.ToList();
//wTemp.RemoveAll(w => w.WorkerName == null);
//ShiftsDBManagerInstance.UpdateWorkers(wTemp);
//UpdateWorkers();
MessageBox.Show("השינוי עודכן בהצלחה");
Changed = false;
for (int i = 0; i < Workers.Count(); i++)
{
TableList[Workers[i].ID] = new ObservableCollection<ShiftsExt>(ShiftsDBManagerInstance.SelectShifts(Workers[i].ID));
}
if (TableList[WorkerRowSelectedRowItem.ID].Any())
{
Shifts = new ObservableCollection<ShiftsExt>(TableList[WorkerRowSelectedRowItem.ID].Select(se => se));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I forget to update my main map
I have create
private ObservableCollection<ShiftsExt> CopyActualList() {
return new ObservableCollection<ShiftsExt>(Shifts.Select(se => se));
}
And update my map as TableList[workerRowSelectedRowItem.ID] = CopyActualList();
in
private void UpdateAllShiftDataAction()
{
TableList[WorkerRowSelectedRowItem.ID] = CopyActualList();
for (int i = 0; i < TableList.Count; i++)
{
if (TableList.ElementAt(i).Value.Any()) {
ShiftsDBManagerInstance.UpdateShifts(TableList.ElementAt(i).Value.ToList());
}
}
MessageBox.Show("השינוי עודכן בהצלחה");
Changed = false;
for (int i = 0; i < Workers.Count(); i++)
{
TableList[Workers[i].ID] = new ObservableCollection<ShiftsExt>(ShiftsDBManagerInstance.SelectShifts(Workers[i].ID));
}
if (TableList[WorkerRowSelectedRowItem.ID].Any())
{
Shifts = new ObservableCollection<ShiftsExt>(TableList[WorkerRowSelectedRowItem.ID].Select(se => se));
}
}
and
public Worker WorkerRowSelectedRowItem
{
get { return workerRowSelectedRowItem; }
set {
if(workerRowSelectedRowItem != null && TableList != null)
{
TableList[workerRowSelectedRowItem.ID] = CopyActualList();
}
workerRowSelectedRowItem = value;
UpdateShifts();
}
}
I have a WPF window with a listview with textboxes in it, and I want to enable users to switch between textboxes with the TAB key. I created a function that does that, but every time the textbox loses focus, I refresh the listview and so the listview itself gets focused.
I know the problem occurs because of the refresh event because commenting that part will result in the correct element (the textbox) being focused.
I have tried many alternative solutions but none works (prevent item losing focus when refreshing list view in c#; Focus on TextBox after ListView 'SelectionChanged' Event).
It seems there is something problematic in focusing on an that element after the listview is refreshed.
I have tried remembering the index of the item and then focusing on it, after the TAB key was pressed. tried also remembering the focused control and then focusing on it after the refresh.
private void RTB_Reference_LostFocus(object sender, RoutedEventArgs e)
{
int Index = DetailsList.SelectedIndex;
Index = DetailsList.Items.IndexOf(DetailsList.SelectedItem);
try
{
//Get cell value by using sender Object
string inTime = ((System.Windows.Controls.TextBox)sender).Text;
DetailItem item = (DetailItem)DetailsList.Items[Index];
item.Reference = inTime;
UpdateExplanation(item);
}
catch (Exception)
{
}
}
private void RTB_Detail_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
e.Handled = true;
//System.Windows.MessageBox.Show("Tab");
int Idx = DetailsList.SelectedIndex;
System.Windows.Controls.ListViewItem lvi = (System.Windows.Controls.ListViewItem)DetailsList.ItemContainerGenerator.ContainerFromItem(DetailsList.SelectedItem);
GUF.FocusItem(DetailsList, Idx, "RTB_Detail");
//IsLastKeyTAB = true;
}
//else
// IsLastKeyTAB = false;
}
private void UpdateExplanation(DetailItem item)
{
item.Explanation = GetExplanation(item.Reference, item.Detail);
IInputElement focusedControl = Keyboard.FocusedElement;
DetailsList.Items.Refresh();
focusedControl.Focus();
RefreshDetailsList(DetailsList, IsEnglish);
}
expected result is to keep focus on that textbox after the refresh. that does not occur...
EDIT 1
This is the listview xaml:
<ListView FlowDirection="RightToLeft" x:Name="DetailsList" VirtualizingStackPanel.IsVirtualizing="False"
HorizontalAlignment="Stretch" VerticalAlignment="Top"
HorizontalContentAlignment="Center" ScrollViewer.VerticalScrollBarVisibility="Visible" Padding="0 0 0 25"
AllowDrop="True"
ItemsSource="{Binding DetailItem}"
Loaded="ListView_Loaded"
Margin="26,157,23,0"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}" Height="599">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="Margin" Value="4, 4, 4, 4"/>
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="Height" Value="22"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="Transparent" BorderThickness="0" Background="{TemplateBinding Background}">
<GridViewRowPresenter HorizontalAlignment="Stretch" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Width="Auto" Margin="0" Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- <Setter Property="Background" Value="#6B54FF"/> -->
<Setter Property="Foreground" Value="#6B57FF"/>
<Setter Property="BorderThickness" Value="2" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="#6B57FF"/>
<Setter Property="Foreground" Value="#6B57FF" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="סימון" DisplayMemberBinding="{Binding Mark}"/>
<!--
<GridViewColumn Width="30" >
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button Style="{StaticResource PlusButtonStyle}" x:Name="buttonPlusDocument" Click="buttonPlusDocument_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
-->
<GridViewColumn Header="הפניה במסמך" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="RTB_Reference" BorderBrush="#5f27ff" BorderThickness="1" KeyDown="RTB_Reference_KeyDown" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True"
LostFocus="RTB_Reference_LostFocus" GotFocus="RTB_Reference_GotFocus">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderBrush" Value="#5f27ff"/>
<Setter Property="BorderThickness" Value="1" />
</Style>
</TextBox.Resources>
</TextBox>
<!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="פרט" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="RTB_Detail" BorderBrush="#5f27ff" BorderThickness="1" HorizontalAlignment="Stretch" Height="20" Margin="0" Padding="0" FontSize="12" IsEnabled="True"
KeyDown="RTB_Detail_KeyDown" LostFocus="RTB_Detail_LostFocus" GotFocus="RTB_Detail_GotFocus">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderBrush" Value="#5f27ff"/>
<Setter Property="BorderThickness" Value="1" />
</Style>
</TextBox.Resources>
</TextBox>
<!--DataContext="{Binding SelectedItem, ElementName=ListViewAppendixNameList, Mode=TwoWay}"-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="הסבר" Width="350" DisplayMemberBinding="{Binding Explanation}"/>
<GridViewColumn Width="30" >
<GridViewColumn.CellTemplate>
<DataTemplate >
<Button Style="{StaticResource DeleteButtonStyle}" x:Name="BT_DeleteDetail" Click="BT_DeleteDetail_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
This is the details class:
public class DetailItem
{
public string Mark { get; set; }
public string Reference { get; set; }
public string Detail { get; set; }
public string Explanation { get; set; }
}
I refresh the listview so that the Explanation text will be updated.
I don't went through your whole code but only those parts that are relevant to your question. To simplify things, I will use the attached property TabNavigation and the data binding feature. The binding will automatically update the control values so that the refreshing of the ListView and the focus handling becomes redundant.
Remark: To shorten the XAML code I will only show the relevant code portions.
Tab navigation
Set the attached property KeyboardNavigation.TabNavigation on the ListView to Continue so that the focus will loop through the list:
<ListView x:Name="DetailsList"
KeyboardNavigation.TabNavigation="Continue"
...
</ListView>
You can control which elements can receive focus on Tab key pressed by setting the property IsTabStop on the elements. The default value is True. So set it to False on those elements you wish to exclude. It's useful to disable Tab focus on the ListViewItem itself so that the Tab key is only effective for the containing controls:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsTabStop"
Value="False" />
...
</Style>
</ListView.ItemContainerStyle>
If you wish to exclude the Button as well, so that you only jump between the TextBox elements, set the Button's IsTabStopproperty to False as well:
<GridViewColumn Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button x:Name="BT_DeleteDetail"
IsTabStop="False"
...
/>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
If you also wish to change the order the elements will receive focus on Tab key pressed, you can do it by setting the element's TabIndex property. A control with a lower tab index receives focus before a control with a higher index.
Binding the TextBox
To enable binding to a data model it is required that the data model (view model) implements INotifyPropertxChanged:
public class DetailItem : INotifyPropertyChanged
{
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private string mark;
public string Mark
{
get => this.mark;
set
{
if (value == this.mark) return;
this.mark = value;
OnPropertyChanged();
}
}
private string reference;
public string Reference
{
get => this.reference;
set
{
if (value == this.reference) return;
this.reference = value;
OnPropertyChanged();
}
}
private string detail;
public string Detail
{
get => this.detail;
set
{
if (value == this.detail) return;
this.detail = value;
OnPropertyChanged();
}
}
private string explanation;
public string Explanation
{
get => this.explanation;
set
{
if (value == this.explanation) return;
this.explanation = value;
OnPropertyChanged();
}
}
}
In order access the view model from your DataTemplate set its DataType property to the type of the view model (DetailItem). Remember to always set the DataType of a DataTemplate. Then add the Binding to the TextBox. Note that the Mode (direction) of the binding is set to OneWayToSource. This limits the binding to set data from TextBoxto DetailItem only:
<GridViewColumn.CellTemplate>
<DataTemplate DataType="viewModels:DetailItem">
<TextBox x:Name="RTB_Reference"
Text="{Binding Reference, Mode=OneWayToSource}"
...
</TextBox>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumn.CellTemplate>
<DataTemplate DataType="viewModels:DetailItem">
<TextBox x:Name="RTB_Detail"
Text="{Binding Detail, Mode=OneWayToSource}"
...
</TextBox>
...
</DataTemplate>
</GridViewColumn.CellTemplate>
The final step is to update the DetailItem.Explanation property. To do this, we move the UpdateExplanation() method into the view model DetailItem. Since we use binding we can now get rid of all the ListView refresh and focus logic inside, so the method becomes smaller. Note that since we moved the method into the DetailItem class, we can also remove the parameter of the method:
private void UpdateExplanation()
{
this.Explanation = GetExplanation(this.Reference, this.Detail);
}
The UpdateExplanation() method is invoked directly from the setter of the Reference and Detail property, so that everytime these properties are changed the Explanation value will be updated. With the updated Reference and Detail setter the final version of the DetailItem class will then look as followed:
public class DetailItem : INotifyPropertyChanged
{
private void UpdateExplanation()
{
this.Explanation = GetExplanation(this.Reference, this.Detail);
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private string mark;
public string Mark
{
get => this.mark;
set
{
if (value == this.mark) return;
this.mark = value;
OnPropertyChanged();
}
}
private string reference;
public string Reference
{
get => this.reference;
set
{
if (value == this.reference) return;
this.reference = value;
OnPropertyChanged();
UpdateExplanation();
}
}
private string detail;
public string Detail
{
get => this.detail;
set
{
if (value == this.detail) return;
this.detail = value;
OnPropertyChanged();
UpdateExplanation();
}
}
private string explanation;
public string Explanation
{
get => this.explanation;
set
{
if (value == this.explanation) return;
this.explanation = value;
OnPropertyChanged();
}
}
}
Try Below Code. Enjoy !!!
<Grid>
<ListView Margin="45.5,47.5,41.167,0" Name="lvtestListview" Height="188.333" VerticalAlignment="Top">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<!--<Setter Property="IsSelected" Value="{Binding IsGroovy}"/>-->
<Style.Triggers>
<Trigger Property="IsSelected" Value="true" >
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Background" Value="LightGreen" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Item Code" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBlock Text="{Binding ItemCode}" Margin="6,3"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Item Name" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBox Loaded="txtBarcode_Loaded" Text="{Binding ItemName}" Name="txtBarcode" Focusable="{Binding IsFocused}" Cursor="IBeam" />
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Next Item" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Border BorderBrush="Gray" BorderThickness=".5" Margin="-6,-3">
<TextBox TabIndex="3" Name="txtgvPL1" KeyDown="txtgvPL1_KeyDown" Text="{Binding PL1}" Cursor="IBeam"/>
</Border>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
code behind
public class ClassA : INotifyPropertyChanged
{
public string ItemCode { get; set; }
public string ItemName { get; set; }
private bool isFocused;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsFocused
{
get
{
return isFocused;
}
set
{
if (value != isFocused)
{
isFocused = value;
RaisePropertyChanged("IsFocused");
}
}
}
private void RaisePropertyChanged(string propName)
{
PropertyChangedEventHandler eh = PropertyChanged;
if (eh != null)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
public partial class MainWindow : Window
{
List<ClassA> lstClassA = new List<ClassA>();
public MainWindow()
{
InitializeComponent();
BindGrid();
}
private void BindGrid()
{
ClassA ca = new ClassA();
ca.ItemCode = "A";
ca.IsFocused = true;
lstClassA.Add(ca);
lvtestListview.ItemsSource = lstClassA;
}
private void txtBarcode_Loaded(object sender, RoutedEventArgs e)
{
Keyboard.Focus(((TextBox)sender));
}
private void txtgvPL1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter )
{
ClassA ca = new ClassA();
ca.ItemCode = "B";
ca.IsFocused = true;
lstClassA.Add(ca);
foreach (ClassA a in lstClassA)
{
if (a.ItemCode == "A")
{
a.IsFocused = false;
}
}
lvtestListview.ItemsSource = lstClassA;
lvtestListview.Items.Refresh();
}
}
}
I am working on wpf mvvm pattern.
I have designed multiple UserControls for different checkboxes. On selection of a checkbox, the corresponding UserControl should get loaded in the right side.
For a single checkbox, I have added the UserControl in MainView.xaml as this:
<StackPanel>
<UserControl Name="CCS01" ScrollViewer.CanContentScroll="True" Margin="5" >
<local:CCS01 HorizontalAlignment="Left"></local:CCS01>
</UserControl>
</StackPanel>
I have stored the list of Checkboxes in a datagrid in a different UserControl like this
<DataGrid Width="150" Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="40" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="85" Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The viewmodel class is like this:
[![private string _usecaseName;
public string UsecaseName
{
get { return _usecaseName; }
set
{
_usecaseName = value != null ? value.Trim() : null;
OnPropertyChanged("UsecaseName");
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value != null ? value.Trim() : null;
OnPropertyChanged("Description");
}
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}][1]][1]
In short, I want to divide the page into two columns, on the left I have a list of UserControls and right I want to view the selected UserControl (only one can be selected) and how to bind the selected checkbox with the respective UserControl in ViewModel class.
For reference, I am adding the image of UI here:
You try Something like this :
*.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" ItemsSource="{Binding Path=UsecaseListItems}"
Name="dataGridCustomers" CanUserResizeColumns="False" CanUserResizeRows="False"
SelectionChanged="dataGridCustomers_SelectionChanged">
<DataGrid.Columns>
<DataGridCheckBoxColumn Width="auto" Header="Select" Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}">
</DataGridCheckBoxColumn>
<DataGridTextColumn Width="auto" Binding="{Binding UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="Black"></Setter>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Border Grid.Column="1" BorderThickness="1" BorderBrush="Gray">
<Grid Grid.Column="1" x:Name="HostGrid" Margin="5">
</Grid>
</Border>
</Grid>
*.cs (code behind)
private void dataGridCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var SelectedItem = dataGridCustomers.SelectedItem;
string UserControlName = ((UseCase)SelectedItem).UsecaseName;
Assembly ass = Assembly.GetExecutingAssembly();
foreach (var item in ass.GetTypes())
{
if (item.Name == UserControlName)
{
UserControl uc = (UserControl)Activator.CreateInstance(item,null);
HostGrid.Children.Add(uc);
}
}
}
If you can remove the checkbox, it would be better. Because the CheckBox allows multi-selection
Create a class MyControl with 2 properties string Name (will be binded to your datagrid) and UserControl Control it will be your control.
Create a list of your controls.
In your ViewModel create a MyControl SelectedControl property, this will tell you what's the selected controls, and bind it to the datagrid
In your View add a ContentControl and bind it to: SelectedControl.Control
I'm trying to style my grid a little bit. I want to color rows based on information in the each row. I've got this nasty error during this operation. Moreover this is happening during application startup. This my first steps in WPF I'm trying to make some useful logger, but now I hit the huge wall.
Error:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
It is worth mentioning that I'm using static list as ItemSource to grid. Below some code of mine.
<DataGrid DockPanel.Dock="Bottom" Height="Auto" ItemsSource="{Binding Source={x:Static Log:Logger.LogCollection}, Mode=OneWay}" FontSize="12" FontFamily="Segoe UI">
<i:Interaction.Behaviors>
<fw:ScrollGridView/>
</i:Interaction.Behaviors>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding LogType}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid>
</DockPanel>
And the class
public static class Logger
{
private static ObservableCollection<LogMessage> _logCollection = new ObservableCollection<LogMessage>();
public static ObservableCollection<LogMessage> LogCollection
{
get { return Logger._logCollection; }
set
{
if (_logCollection.Count > 500)
{
_logCollection.RemoveAt(_logCollection.Count - 1);
}
Logger._logCollection = value;
}
}
public static void Log(string message)
{
Log(LogType.Info, message, null);
}
public static void Log(LogType type, string message, string exception)
{
LogCollection.Add(new LogMessage { Type = type, Time = DateTime.Now, Message = message, Exception = exception });
}
}
LogMessage:
public enum LogType
{
Debug,
Warning,
Error,
Info
}
public class LogMessage
{
public LogType Type { get; set; }
public DateTime Time { get; set; }
public string Message { get; set; }
public string Exception { get; set; }
}
Thanks!
Make sure you define your Columns for DataGrid in DataGrid.Columns like below and not directly within DataGrid and define your Style in DataGrid.Resource
<DataGrid>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding LogType}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn/>
</DataGrid.Columns>
</DataGrid>
It helped!!! Now it looks like this:
<DataGrid DockPanel.Dock="Bottom" Height="Auto" ItemsSource="{Binding Source={x:Static Log:Logger.LogCollection}, Mode=OneWay}" FontSize="12" FontFamily="Segoe UI" AutoGenerateColumns="False">
<i:Interaction.Behaviors>
<fw:ScrollGridView/>
</i:Interaction.Behaviors>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Info">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn MinWidth="30" Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Width="16"
Height="16"
Source="{Binding Path=Type,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ImageTypeConverter}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Time"
Binding="{Binding Time}"/>
<DataGridTextColumn Header="Message"
Binding="{Binding Message}"
Width="1200">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Exception"
Binding="{Binding Exception}"
Width="1200">
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
And I have another one question. Before I have added DataGrid.Resources my behavior worker perfectly and after that the event stopped firing. It is strange because I didn't change way of adding item to my grid source.
public class ScrollGridView : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectionChanged += new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is DataGrid)
{
DataGrid grid = (sender as DataGrid);
if (grid.Items.Count > 0)
{
var border = VisualTreeHelper.GetChild(grid, 0) as Decorator;
if (border != null)
{
var scroll = border.Child as ScrollViewer;
if (scroll != null && !scroll.IsMouseOver) scroll.ScrollToEnd();
}
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.SelectionChanged -= new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
}
}