So I have a DataTable that I bind to the DataGrid in XAML.
User is allowed to add, modify and remove rows for a table. I would like to mark rows with a specific colour, depending on the action that user makes. For instance, if user adds a row, that row will be marked as Green. If user modifies a row, that row will then be marked as orange. And if user removes the row, that row will be marked as red.
The problem that I have is that the removed row is no longer visible once I call row.Delete(); from a view model.
Is there a way to keep a DataRow marked for removal shown in a DataGrid? I know how to achieve the row background effect, depending on user action. The only problem is I don't know how to keep the deleted row visible. The idea is that user can either revert changes or apply them and that's when the pending deletion row should be actually deleted.
EDIT (Added example on how I update row background colour):
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsSelected}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Background" Value="IndianRed" TargetName="DGR_Border"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontWeight" Value="Bold"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
I think, when user marks row for deleting - you should save it index somewhere (int[] array or List<int> for example), then call yourDataGridView.Rows.RemoveAt(index) for each element in that collection when user finished working with table.
Maybe something like:
//Collection for rows indexes which will be deleted later
List<int> rowsToDelete = new List<int>();
//Call this when user want to delete element
private void MarkElementsToRemove()
{
if (yourDataGrid.SelectedItems.Count > 0)
{
//Get selected by user rows
for (int i = 0; i < yourDataGrid.SelectedItems.Count; ++i)
{
DataGridRow row = (DataGridRow)yourDataGrid.SelectedItems[i];
//Fill rows background with some color to mark them visually as "deleted"
row.Background = new SolidColorBrush(Color.FromRgb(255, 0, 0));
//Get row index to remove it later and add it to collection
rowsToDelete.Add(row.GetIndex());
}
}
}
// Call this when user finished work with DataGrid and items may be removed
private void RemoveMarkedElements()
{
foreach (int index in rowsToDelete)
{
yourDataGrid.Items.RemoveAt(index);
}
rowsToDelete.Clear();
}
Instead of index you may save whole DataGridRow and call yourDataGrid.Remove(wholeRow);.
And for reverse deletion, you just unmark it by removing color and removing row index or whole row from a collection.
If I understood you correctly, you need to use the Delete key not to delete lines, but to put a marker on them.
And in the DataGrid, you need to highlight color the rows marked with this marker.
You have not shown your table, so I will demonstrate in my simple conciliation.
The example uses the BaseInpc and RelayCommand classes.
In addition to them, the command extension method is used:
using System.Windows.Input;
namespace Simplified
{
public static class CommandExtensionMethods
{
public static bool TryExecute(this ICommand command, object parameter)
{
bool can = command.CanExecute(parameter);
if (can)
command.Execute(parameter);
return can;
}
public static bool TryExecute(this ICommand command)
=> TryExecute(command, null);
}
}
ViewModel:
using Simplified;
using System.Data;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public class DrdViewModel : BaseInpc
{
public DataTable Table { get; } = new DataTable();
public DrdViewModel()
{
Table.Columns.Add("Name", typeof(string));
Table.Columns.Add("Value", typeof(int));
Table.Columns.Add("Marked for deletion", typeof(bool));
foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
{
var row = Table.NewRow();
row[0] = name;
row[1] = Table.Rows.Count;
row[2] = Table.Rows.Count % 2 == 1;
Table.Rows.Add(row);
}
}
private ICommand _markRemoveChangeCommand;
private bool _isRemoveRowsImmediately;
public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
row => row[2] = !(bool)(row[2] ?? false),
row => !IsRemoveRowsImmediately
));
public bool IsRemoveRowsImmediately
{
get => _isRemoveRowsImmediately;
set => Set(ref _isRemoveRowsImmediately, value);
}
}
}
Window XAML:
<Window x:Class="DeferredRowDeletion.DrdWindow"
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:DeferredRowDeletion"
mc:Ignorable="d"
Title="DrdWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:DrdViewModel/>
</FrameworkElement.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<CheckBox Content="Removw Rows Immediately"
IsChecked="{Binding IsRemoveRowsImmediately}"
Margin="5"/>
<DataGrid x:Name="dataGrid" Grid.Row="1"
ItemsSource="{Binding Table, Mode=OneWay}"
AutoGeneratingColumn="OnAutoGeneratingColumn"
CanUserDeleteRows="{Binding IsRemoveRowsImmediately}"
PreviewKeyDown="OnPreviewKeyDown">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding [Marked for deletion]}" Value="true">
<Setter Property="Background" Value="HotPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
Code Behind Window:
using Simplified;
using System.Data;
using System.Windows;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public partial class DrdWindow : Window
{
public DrdWindow()
{
InitializeComponent();
}
private void OnAutoGeneratingColumn(object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyName == "Marked for deletion")
e.Cancel = true;
}
private void OnPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
DrdViewModel viewModel = (DrdViewModel)DataContext;
var rowView = dataGrid.CurrentItem as DataRowView;
if (rowView != null && !rowView.IsEdit)
viewModel.MarkRemoveChangeCommand.TryExecute(rowView.Row);
}
}
}
}
If you are unable to use this example, then write the reason and add details to the explanation of your question.
The answer is supplemented by clarifications for the added details:
I think I should've mentioned that I use DataRow's RowState property bound to the DataTrigger to update row background colour. Added details to the question.
To control the visibility of rows, you need to change the value of the DataTable.DefaultView.RowStateFilter property.
This is not hard to do in the ViewModel.
But an additional problem is that the RowState property does not notify about its change.
So the trigger binding won't work just like that.
In my example, I solved this by calling Items.Refresh ().
Perhaps you are using a different solution since you have not written about any problems associated with this.
using Simplified;
using System.Data;
using System.Windows.Input;
namespace DeferredRowDeletion
{
public class ShowDeletedRowsViewModel : BaseInpc
{
public DataTable Table { get; } = new DataTable();
public ShowDeletedRowsViewModel()
{
Table.Columns.Add("Name", typeof(string));
Table.Columns.Add("Value", typeof(int));
foreach (var name in new string[] { "First", "Second", "Third", "Fourth", "Fifth" })
{
var row = Table.NewRow();
row[0] = name;
row[1] = Table.Rows.Count;
Table.Rows.Add(row);
}
// Commits all the changes
Table.AcceptChanges();
Table.Rows[1].Delete();
Table.Rows[3].Delete();
// Show Deleded Rows
IsVisibilityDelededRows = true;
}
private ICommand _markRemoveChangeCommand;
private bool _isVisibilityDelededRows;
public ICommand MarkRemoveChangeCommand => _markRemoveChangeCommand
?? (_markRemoveChangeCommand = new RelayCommand<DataRow>(
row => IsVisibilityDelededRows ^= true,
row => !IsVisibilityDelededRows
));
public bool IsVisibilityDelededRows
{
get => _isVisibilityDelededRows;
set => Set(ref _isVisibilityDelededRows, value);
}
protected override void OnPropertyChanged(string propertyName, object oldValue, object newValue)
{
base.OnPropertyChanged(propertyName, oldValue, newValue);
if (propertyName == nameof(IsVisibilityDelededRows))
{
// Change the row filter if the associated property has changed
if (IsVisibilityDelededRows)
{
Table.DefaultView.RowStateFilter |= DataViewRowState.Deleted;
}
else
{
Table.DefaultView.RowStateFilter &= ~DataViewRowState.Deleted;
}
}
}
}
}
<Window x:Class="DeferredRowDeletion.SdrWindow"
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:DeferredRowDeletion" xmlns:data="clr-namespace:System.Data;assembly=System.Data"
mc:Ignorable="d"
Title="SdrWindow" Height="450" Width="800">
<FrameworkElement.DataContext>
<local:ShowDeletedRowsViewModel/>
</FrameworkElement.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel>
<CheckBox x:Name="cbAutoRefresh" Content="Auto Items.Refresh()" IsChecked="True" Margin="5"/>
<CheckBox Content="Visibility Deleded Rows"
IsChecked="{Binding IsVisibilityDelededRows}"
Margin="5"/>
</StackPanel>
<DataGrid x:Name="dataGrid" Grid.Row="1"
ItemsSource="{Binding Table, Mode=OneWay}"
PreviewKeyUp="OnPreviewKeyUp">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Row.RowState, Mode=OneWay}"
Header="RowState"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Row.RowState}" Value="{x:Static data:DataRowState.Deleted}">
<Setter Property="Background" Value="HotPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</Grid>
</Window>
private void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete && cbAutoRefresh.IsChecked == true)
dataGrid.Items.Refresh();
}
Related
I just recently started learning MVVM. I hope that a solution to this problem will come.
In my application, the user is authorized in the system, after which a window with a table opens. Users are divided into roles: Administrator and Employee. I want the Employee to be unable to see a certain column (ID).
I have an AuthorizationMeth class, where the IDRoleAuthorization variable stores role ID of the authorized user. How can I now use this value to hide the column ID? In my case if IDRoleAuthorization = 2 to hide column ID
Found solutions using the Freezable class and creating a FrameworkElement in XAML but I can't figure out how this is solved for my problem.
Methods/AuthorizationMeth.cs
public class AuthorizationMeth
{
public static int IDRoleAuthorization;
public bool Enter(string login, string password)
{
Intis6Context db = new Intis6Context();
if (login == "" || password == "")
{
MessageBox.Show("You have not completed all fields", "Authorization", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
var auth_check = db.Users.AsNoTracking().FirstOrDefault(ch => ch.Login == login && ch.Password == password);
if (auth_check == null)
{
MessageBox.Show("Login or password entered incorrectly", "Authorization", MessageBoxButton.OK, MessageBoxImage.Error);
return false;
}
IDRoleAuthorization = auth_check.IdRole;
return true;
}
}
View/ContractView.xaml
<DataGrid Background="White" AutoGenerateColumns="False" EnableColumnVirtualization="True" EnableRowVirtualization="True"
ItemsSource="{Binding AllContrsupl_saleDTO, IsAsync=True}"
Grid.Row="0">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=Cnssid}"/>
<DataGridTextColumn Header="Author" Binding="{Binding Path=FULLNAMEstaff}"/>
<DataGridTextColumn Header="Type" Binding="{Binding Path=typeTable}"/>
Have you tried something like this?
If you want to Hide the "ID" column try this:
You need to assign a x:Name to your DataGrid control first. For example "myDataGrid" Then you can do this in Code behind.
if(IDRoleAuthorization == 2)
{
myDataGrid.Columns[0].Visibility = Visibility.Collapsed;
}
Hope this helps.
Don't expose a public static int IDRoleAuthorization field. Such a field must be a property (never define public fields) and at least read-only, and a read-only instance property (non-static) at best. Additionally, don't expose the numeric value, but a bool property e.g., IsAuthorized. The logic to determine whether the numeric code evaluates to an authorized user must be encapsulated and not spread across the application. External classes must depend on the result of this evaluation only.
The DataGridColumn definitions are not part of the visual tree. They are not rendered. they are just placeholders that contain information about the column, which will be generated by the DataGrid later. The actual column consists of a DataGridColumnHeader and DataGridCell elements.
Because of this, you can't configure a Binding on the DataGridColumn.Visbility property.
You can now simply toggle the cells and their associated header (which will not remove the column, but the values of the header and cells):
<!-- ToggleButton to simulate the IsAuthorized property -->
<ToggleButton x:Name="ToggleButton" Content="Hide/show column content" />
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="Dynamic Column">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Static Column" />
</DataGrid.Columns>
</DataGrid>
To remove the column completely in your scenario (no auto-generated columns), you must remove it manually from the DataGrid.Columns collection or toggle the column definitions Visibilty explicitly (by accessing the DataGrid.Columns).
Add and AuthorizationChanged event to your view model class and make the view listen to it. In the event handler set the columns visibility or remove/add the column.
Alternatively, write a simple attached behavior:
DataGridHelper.cs
class DataGridHelper : DependencyObject
{
public static string GetHidableColumnIndices(DependencyObject attachingElement) => (string)attachingElement.GetValue(HidableColumnIndicesProperty);
public static void SetHidableColumnIndices(DependencyObject attachingElement, string value) => attachingElement.SetValue(HidableColumnIndicesProperty, value);
public static readonly DependencyProperty HidableColumnIndicesProperty = DependencyProperty.RegisterAttached(
"HidableColumnIndices",
typeof(string),
typeof(DataGridHelper),
new PropertyMetadata(default(string), OnHidableColumnIndicesChanged));
public static Visibility GetColumnVisibility(DependencyObject attachingElement) => (Visibility)attachingElement.GetValue(ColumnVisibilityProperty);
public static void SetColumnVisibility(DependencyObject attachingElement, Visibility value) => attachingElement.SetValue(ColumnVisibilityProperty, value);
public static readonly DependencyProperty ColumnVisibilityProperty = DependencyProperty.RegisterAttached(
"ColumnVisibility",
typeof(Visibility),
typeof(DataGridHelper),
new PropertyMetadata(default(Visibility), OnColumnVisibilityChanged));
private static void OnColumnVisibilityChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not DataGrid dataGrid)
{
throw new ArgumentException("Attaching element must be of type DataGrid.");
}
ToggleColumnVisibility(dataGrid);
}
private static void OnHidableColumnIndicesChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (attachingElement is not DataGrid dataGrid)
{
throw new ArgumentException("Attaching element must be of type DataGrid.");
}
ToggleColumnVisibility(dataGrid);
}
private static void ToggleColumnVisibility(DataGrid dataGrid)
{
IEnumerable<int> columnIndices = GetHidableColumnIndices(dataGrid)
.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(numericChar => int.Parse(numericChar));
foreach (int columnIndex in columnIndices)
{
dataGrid.Columns[columnIndex].Visibility = GetColumnVisibility(dataGrid);
}
}
}
Usage example
<DataGrid DatGridHelper.HidableColumnIndices="0,3,8"
DataGridHelper.ColumnVisiblility="{Binding IsAuthenticated, Converter={StaticResource BooleanToVisibilityConverter}}" />
I am getting my buttons to appear, but their styling is not displaying and i'm not sure why.
Below is the XAML code and how it is defined in the view model.
private ObservableCollection<Button> myButtons;
public ObservableCollection<Button> MyButtons
{
get { return myButtons; }
set
{
if (myButtons == null)
{
myButtons = value; OnPropertyChanged("MyButtons");
}
}
}
private void PopulateButtons()
{
List<Button> buttonsToAdd = new List<Button>();
List<string> buttonsToAdd = new List<string>();
foreach (var item in SettingsSingleton.RowColumnOptions)
{
int total = item.Key;
Tuple<int, int> rowColumn = item.Value;
buttonsToAdd.Add((total).ToString());
}
MyButtons = new ObservableCollection<Button>(buttonsToAdd);
}
And XAML is
<StackPanel DockPanel.Dock="Top"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ItemsControl ItemsSource="{Binding MyButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Create_Click}" CommandParameter="{Binding Content}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
I also get a weird warning/error:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='Button'
Can anyone help? Thank you.
EDIT:
My Create_Click command and the functions that then get called by it.
private ICommand createClickCommand;
public ICommand Create_Click
{
get
{
if (createClickCommand == null)
{
createClickCommand = new RelayCommand(CreateGrid);
}
return createClickCommand;
}
}
private void CreateGrid(object param)
{
Generate(param);
}
And RowColumnOptions is basically just a tuple of int, int, where we define a row and column. This is used for the Grid creation. This is where RowColumnOptions gets populated.
public static class SystemSettingsSingleton
{
public static Dictionary<int, Tuple<int, int>> RowColumnOptions = new Dictionary<int, Tuple<int, int>>();
public static void SetOptions(List<string> Options)
{
// Let's parse our options
foreach (var option in Options)
{
var rowAndColumnSettings = option.Split('x');
// Check that we have both row and column count
int rowCount, columnCount = 0;
if (rowAndColumnSettings.Length == 2 && int.TryParse(rowAndColumnSettings[0], out rowCount)
&& int.TryParse(rowAndColumnSettings[1], out columnCount))
{
RowColumnOptions.Add( (rowCount * columnCount),
new Tuple<int, int>(rowCount, columnCount) );
}
}
}
}
That SetOptions method is coming from a static class.
You shouldn't have an observable collection of Button. That's never a good idea. It's telling you it's ignoring the ItemTemplate because you are giving it controls in the collection, which is unnecessary. Then you're trying to create another button in the item template. Templates in WPF don't mean what you think they do: They don't style content, they create content. Don't create buttons in your viewmodel, create them in your item template. Your ObservableCollection should just provide the information the item template will need to create the buttons you want to see. Here, that's just one string.
But it turns out that your options really consist of tuples. You want to display their products in the button content, and you want to pass the tuples themselves as the command parameter. This is easy.
public class ViewModel : ViewModelBase
{
public Dictionary<int, Tuple<int, int>> RowColumnOptions
=> SystemSettingsSingleton.RowColumnOptions;
private void PopulateButtons()
{
OnPropertyChanged(nameof(RowColumnOptions));
}
// I don't know what your relay command class looks like, I just tossed one
// together that has an Action<object>
public ICommand Create_Click { get; } = new RelayCommand(param =>
{
var tuple = param as Tuple<int, int>;
MessageBox.Show($"Parameter: {tuple.Item1} x {tuple.Item2}");
});
}
And the ItemsControl:
<ItemsControl ItemsSource="{Binding RowColumnOptions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--
The DataContext here is a KeyValuePair<int, Tuple<int, int>>
The product of x * y was the Key, so that's what we'll display in the button's
Content.
We could display all three values if we wanted to.
We want to pass the tuple to the command, and that's the Value of the KeyValuePair.
So we bind that to CommandParameter
-->
<Button
Command="{Binding DataContext.Create_Click, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Value}"
Content="{Binding Key}"
>
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource NiceStyleButton}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="20"/>
<Setter Property="Padding" Value="6"/>
<Setter Property="FontSize" Value="42"/>
</Style>
</Button.Style>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Some questions linger in my mind: What if you have both "2x6" and "3x4" in that initial list of options? The first one that comes in will be replaced by the second. Is that the desired behavior?
In listbox binding the values from ItemsSource. If doubleclick any item from listbox, it will clear the itemsSource value it will add the combobox control to the specified listboxselected index. How to achieve this?
Now it shows like "Element already has a logical parent. It must be detached from the old parent before it is attached to a new one"
//Get the index value of selected Item
var index = lstbxindex.Items.IndexOf(lstbxindex.SelectedItem);
m_combobox.Visibility = Visibility.Visible;
projectInformationList = null;
lstbxindex.ItemsSource = null;
lstbxindex.Items.Clear();
lstbxindex.Items.Insert(index, m_combobox); //InvalidOperationException thrown
m_combobox.Focus();
To Remove the Items from list I used this code,
IEditableCollectionView items = lstbxindex.Items;
if (items.CanRemove)
{
items.Remove(lstbxindex.SelectedItem);
}
I don't know how to add the control in listbox selectedindex while itemsSource is in use.
I tried ->
listbox.Items.Add(combox); it shows, can't able to add items while itemsSource is in use but in this code I didn't mention the selected index. But I want to add a control for listbox selectedindex position while double click any items from listbox.
Edit
I tried to add the items without using itemsSource like,
foreach (DTorow rowdata in table.Rows)
{
lstbox.Items.Add(rowdata .Name);
}
But it did not show the values in listbox. If it is shows the value in listbox simply add a combobox into specified index using this code listbox.Items.Insert(0"combobox) it will not shows invalidException(itemsSource is in use can't able to add new item).
Here is a simple example of how to do it with the standard WPF approach, i.e. MVVM and data templating. As already said in a comment to your previous question, you'll have to read the Data Binding Overview and Data Templating Overview articles to understand it, but this is unavoidable if you want to learn WPF.
The view model and the MainWindow code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Items.Add(new DataItem { Text = "Item 1" });
vm.Items.Add(new DataItem { Text = "Item 2" });
vm.Items.Add(new DataItem { Text = "Item 3" });
vm.Items.Add(new DataItem { Text = "Item 4" });
DataContext = vm;
}
}
public class DataItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string text;
public string Text
{
get { return text; }
set
{
text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
}
public class ViewModel
{
public ObservableCollection<DataItem> Items { get; }
= new ObservableCollection<DataItem>();
}
And the XAML with two different ControlTemplates of a ListBoxItem, depending on
whether it's selected or not:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Margin="5,3" Text="{Binding Text}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBox Text="{Binding Text}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I am using WPF DataGrid and I would like to allow the user to select an entire column by clicking on the column header. I currently have the SelectionUnit set to CellOrRowHeader because I want the same functionality for rows (which works great). Is there an easy way to also allow selection of columns by clicking the column header?
You have many options. one is create your own Template for DataGrid's Header Style. Inside the DataTemplate tag you can change the template of the headers. (you can replace the Button with a TextBlock or anything you want.)
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader" >
<Setter Property="Foreground" Value="Blue" />
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate>
<Button Content={Binding Content}" MouseDown="mouseDownEventHandler">
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
or if you are using DataGrid.Columns to populate your columns and you need to set each of them separately, you can use this:
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn HeaderTemplate="{StaticResource MyTemplate1"/>
<DataGridHyperlinkColumn HeaderTemplate="{StaticResource MyTemplate2"/>
</DataGrid.Columns>
</DatGrid>
where MyTemplate1 and 2 should be defined previously in your control's Resources.
Edit:
The other approach according to this link is to add a PreviewMouseDown to your DataGrid and then find out if the mouse down was happend on the header or not.
This is the simple version of her event handler:
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is DataGridColumnHeader))
{
dep = VisualTreeHelper.GetParent(dep);
}
if (dep == null) return;
if (dep is DataGridColumnHeader)
{
MessageBox.Show(((DataGridColumnHeader)dep).Content.ToString());
}
You could also modify the ColumnHeaderStyle:
In XAML:
<DataGrid>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="Click" Handler="DataGridColumnHeader_OnClick"></EventSetter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
In the code-behind:
private void DataGridColumnHeader_OnClick(object sender, RoutedEventArgs e)
{
var columnHeader = sender as DataGridColumnHeader;
if (columnHeader != null)
{
if (!Keyboard.IsKeyDown(Key.LeftCtrl))
{
dataGrid.SelectedCells.Clear();
}
foreach (var item in dataGrid.Items)
{
dataGrid.SelectedCells.Add(new DataGridCellInfo(item, columnHeader.Column));
}
}
}
In WPF there is no direct support for column selection so, need to Extend existing DataGrid and add custom support for Column Selection.
Below is the code that will allow used to select column on Header click.
For Below code to work following setting is required.
Xaml Code:
<local:DataGridEx SelectionUnit="CellOrRowHeader" CanUserSortColumns="False"/>
C# Code:
public class DataGridEx : DataGrid
{
/// <summary>
/// Holds the column that is selected.
/// </summary>
public object SelectedColumn
{
get { return GetValue(SelectedColumnProperty); }
set { SetValue(SelectedColumnProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedColumn. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedColumnProperty =
DependencyProperty.Register("SelectedColumn", typeof(object),
typeof(DataGridEx), new PropertyMetadata(null));
private T GetVisualParent<T>(DependencyObject child) where T : Visual
{
DependencyObject parent = VisualTreeHelper.GetParent(child);
if (parent == null || parent is T)
{
return parent as T;
}
else
{
return GetVisualParent<T>(parent);
}
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
DataGridColumnHeader dataGridColumnHeader = GetVisualParent<DataGridColumnHeader>(e.OriginalSource as DependencyObject);
if (dataGridColumnHeader == null)
{
return;
}
if (SelectedCells != null && SelectedCells.Count > 0)
{
UnselectAllCells();
SelectedCells.Clear();
}
SelectedColumn = dataGridColumnHeader.Column;
foreach (var item in this.Items)
{
this.SelectedCells.Add(new DataGridCellInfo(item, dataGridColumnHeader.Column));
}
base.OnPreviewMouseDown(e);
}
}
<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Change}" Width="Auto"/>
When the value of Change updates, its column doesn't update to fit the new value. So the column stays too small and the value is clipped.
Any ideas?
The DataGrid will increase column sizes to fit as the data becomes longer, but it does not automatically decrease column sizes when the length of the data decreases. In your example, you're right aligning the 'Change' column, and using the rest of the space for the 'Name' column.
Now, when a 'Change' property grows large enough that it should increase the column's width, the 'Name' column is refusing to shrink to accommodate, so you have to force a refresh yourself.
The following steps should do this for you (I've included a sample app to demo):
1) In your DataGridTextColumn Bindings (all except your * sized column) set NotifyTargetUpdated=True.
2) On your DataGrid, add a handler to the TargetUpdated event.
3) In your TargetUpdated event handler:
-- a) Set the DataGrid's * sized column's width to 0.
-- b) Call the UpdateLayout() method on the DataGrid.
-- c) Set the DataGrid's * sized column's width back to new DataGridLength(1, DataGridLengthUnitType.Star)
Example XAML:
<Window x:Class="DataGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<CollectionViewSource x:Key="MyObjectCollection" />
</Window.Resources>
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Click to Make Item 1s Text Longer" Click="Button_Click" />
<Grid>
<DataGrid x:Name="dg" ItemsSource="{Binding Source={StaticResource MyObjectCollection}}" AutoGenerateColumns="False" TargetUpdated="dg_TargetUpdated">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding First}" Width="1*"/>
<DataGridTextColumn Binding="{Binding Last, NotifyOnTargetUpdated=True}" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DockPanel>
</Window>
Example Code Behind:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
namespace DataGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<MyObject> myObjectList = new ObservableCollection<MyObject>();
public MainWindow()
{
InitializeComponent();
(this.FindResource("MyObjectCollection") as CollectionViewSource).Source = this.myObjectList;
this.myObjectList.Add(new MyObject() { First = "Bob", Last = "Jones" });
this.myObjectList.Add(new MyObject() { First = "Jane", Last = "Doe" });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.myObjectList[0].Last = "BillyOBrian";
}
private void dg_TargetUpdated(object sender, DataTransferEventArgs e)
{
dg.Columns[0].Width = 0;
dg.UpdateLayout();
dg.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
}
}
public class MyObject : INotifyPropertyChanged
{
private string firstName;
public string First
{
get { return this.firstName; }
set
{
if (this.firstName != value)
{
this.firstName = value;
NotifyPropertyChanged("First");
}
}
}
private string lastName;
public string Last
{
get { return this.lastName; }
set
{
if (this.lastName != value)
{
this.lastName = value;
NotifyPropertyChanged("Last");
}
}
}
public MyObject() { }
#region -- INotifyPropertyChanged Contract --
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Contract
}
}
i have had similar problem with my listview, the solution i found on how-to-autosize-and-right-align-gridviewcolumn-data-in-wpf here on stackoverflow.
In my case it was adding this piece of code into collectionchanged event handler of the observable collection the list view was bound to:
void listview_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
// this is a listview control
GridView view = this.View as GridView;
foreach(GridViewColumn c in view.Columns) {
if(double.IsNaN(c.Width)) {
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
}
}
It works for me, although sometimes the user can notice "blink" over the columns.
WPF will just resize a datagrid's column width set to Auto if needed, i.e: the content cannot be displayed entirely. So when the content's width shrinks, the column does not resize as the content can still been seen entirely.
the only way I can see to force wpf to recalculate the columns' widths would be to force them all to 0 and then back to auto in the code behind, with one or two updateLayout() thrown in, but this is not very nice programming :-/
basically, in your code behind:
foreach (DataGridColumn c in dg.Columns)
c.Width = 0;
// Update your DG's source here
foreach (DataGridColumn c in dg.Columns)
c.Width = DataGridLength.Auto;
and you probably need a dg.UpdateLayout() or two somewhere in there (after the update and the setting back to auto probably)
One way you could solve this is by defining the width property of the column in a style setting and binding that setting to a property of the object you are binding to.
<DataGridTextColumn Binding="{Binding Change}" ElementStyle="{StaticResource ChangeColumnStyle}"/>
In your ResourceDictionary:
<Style TargetType="{x:Type DataGridTextColumn }" x:Key="ChangeColumnStyle">
<Setter Property="Width" Value="{Binding ColumnWidth}"
</Style>
ColumnWidth should be a property of your object. Now if you update this property from the setter of your 'Change' property (by using some self-defined algorithm, taking stuff like font into account), and calling:
RaisePropertyChanged("ColumnWidth");
It should update your column width.
public int Change
{
get { return m_change; }
set
{
if (m_change != value)
{
m_change = value;
ColumnWidth = WidthAlgo(numberOfCharacters);
RaisePropertyChanged("Change");
RaisePropertyChanged("ColumnWidth");
}
}
}
Have you tried this?
<DataGridTextColumn Binding="{Binding Path= Id}" Header="ID" IsReadOnly="True" Width="1*" />