I had a checkbox all column inside the datagrid in WPF C#.
<DataGridCheckBoxColumn Binding="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}" CanUserSort="False">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" HorizontalAlignment="Center" Margin="0,0,5,0" IsEnabled="{Binding Path=DataContext.IsCbxAllEnabled,RelativeSource={RelativeSource AncestorType=DataGrid}}"
IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid},UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
When I check the All checkbox, of course, it will mark all the checkboxes, but once I uncheck one checkbox, the All checkbox is still checked. This should be unchecked. How should I do that using WPF C#.
If I understood you correctly - after any change of IsSelected property inside collection item you should update AllSelected value.
So, you need some callback inside all your items(event or Action or any mechanism you want) and change get logic for AllSelected
Here is some draft for item IsSelected property and constructor:
public bool IsSelected {
get { return isSelected; }
set {
isSelected = value;
OnPropertyChanged();
if (globalUpdate != null) globalUpdate();
}
}
public ItemClass(Action globalUpdate, ...your parameters) {
this.globalUpdate = globalUpdate;
...do smth with your parameters
}
Example of usage:
new ItemClass(() => OnPropertyChanged("AllSelected"))
And of course don't forget about AllSelected getter
public bool AllSelected {
get { return YourGridItemsCollection.All(item => item.IsSelected); }
Now when you check manually all items then AllSelected will be automatically checked, and unchecked when you uncheck any item.
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}}" />
WPF : How to add Checked and Unchecked events in the DataGridCheckBoxColumn?
<DataGridCheckBoxColumn Header="Choose" x:Name="choose">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
<EventSetter Event="CheckBox.Unchecked" Handler="OnChecked"/
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Your XAML works fine the problem lies withing you getting the Check Box. You should be able to access the element from the Check Box you triggered with the Unchecked or Checked Event.
Example:
var ch = sender as Checkbox;
var row = data_kala.ItemContainerGenerator.ContainerFromItem(ch) as DataGridRow;
bool ischecked = ch.IsChecked;
if (ischecked) {
row.BackGround = Brushes.Gray;
}
else {
row.BackGround = Brushes.White;
}
Just out of interest....
In case I have a ViewModel with an uninitialized string, which is bound to a Textbox, I can use TargetNullValue to display a default value.
However, I was wondering if I can use the same value to update the string in case it is null?
Basically instead of
set
{
if(value != null) text = value;
else value = "defaultstring";
OnPropertyChanged();
}
just do the same thing from the databinding using TargetNullValue.
You can manipulate the getter as well as the data binding will use the get():
private string text;
public string Text
{
get
{
if (text== null)
return "default value";
else
return this.text;
}
set { this.text= value; }
}
However, if you want to do it in Pure XAML you can use a DataTrigger for this:
<TextBlock Text="{Binding MyText}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock }">
<Style.Triggers>
<DataTrigger Binding="{Binding MyText}" Value="{x:Null}">
<Setter Property="Text" Value="DefaultValue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
My issue is the behavior of the validation errors in the DataGrid. It validates against my model object's property and displays the correct message, but the validation disappears, along with the original value whenever I select a different row.
In the example .gif below, I remove the name (backspace), hit enter (get the validation message), then click to a different row. Whenever the selected row changes I would expect either a) the validation error to remain OR b) the original value to return, but the row stays blank and the validation error is gone until I double click the row. Once I double click, the original value returns.
I would prefer the validation error to persist, but I'll take either at this point.
Here is the datagrid textblock style:
<Style x:Key="datagridElemStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Yellow" />
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Here is the actual DataGridTextColumn:
<DataGridTextColumn Header="NAME"
Width="300"
Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus,
ValidatesOnExceptions=True}"
ElementStyle="{StaticResource datagridElemStyle}"
CanUserReorder="False" />
This is my ViewModel wrapper object (irrelevant parts omitted):
public class PointVM : INotifyPropertyChanged
{
public Point DataContext { get; set; }
public string Name
{
get { return DataContext.Name; }
set
{
if (value != DataContext.Name)
{
DataContext.Name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
}
And finally, here is my Model (irrelevant parts omitted):
public abstract class Point
{
private string _name;
public string Name
{
get { return _name; }
set
{
string trimmedVal = value.Trim();
#region Validation
if (string.IsNullOrEmpty(trimmedVal))
throw new Exception("Name cannot be empty.");
if (Regex.IsMatch(trimmedVal, #"[^A-Za-z0-9\-_ ]$"))
throw new Exception("Invalid character in name.");
if (trimmedVal.Length > 64)
throw new Exception("Name is too long.");
if ((from p in PointList
where p.Name.Equals(trimmedVal, StringComparison.OrdinalIgnoreCase)
select p).Count() > 0)
throw new Exception("Name is already used.");
#endregion
_name = trimmedVal;
}
}
Thanks for your time.
DataGrid validations can be super annoying. When a DataGridTextColumn begins editing, the actual value of the binding is restored, which is why you see a revert when you enter edit mode.
You basically need to stop that call by handling the BeginningEdit event for the DataGrid.
<DataGrid AutoGenerateColumns="False" BeginningEdit="dg_BeginningEdit">
Code-Behind
private void dg_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
e.EditingEventArgs.Handled = true;
}
FYI: Your binding updates on LostFocus. This solution may not work well if you update on PropertyChanged in the future as it changes the order of operation.
I am wondering if anyone could explain me the difference between
binding a selected value of a Collection to a comboBox.
Or Binding the value to a Button Content.
Like that
<ComboBox x:Name="_culturedTitleViewModelSelector" Visibility="Hidden" Style="{StaticResource ResourceKey=_culturedTitleViewModelSelectorStyle}"
ItemsSource="{Binding Path=AvailableCultures, Source={x:Static Localized:ResourcesManager.Current}}"
SelectedValue="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
<Button x:Name="LanguageBtn" Content="{Binding Path=CurrentCulture, Source={x:StaticLocalized:ResourcesManager.Current}}"
The issue is If i Don't use the ComboBox up there, the DependencyProperty I Have in another class is not being called.
But if I Use the comboBox everything works...
Altought the comboBox doesnt do anything it's just a "workarround"
In my CS code when i CLick on my button I DO that :
ResourcesManager.Current.SwitchToNextCulture();
//We use a dummy comboBox to make sure the LanguageBehavior Property is being notified.
_culturedTitleViewModelSelector.SelectedItem = ResourcesManager.Current.CurrentCulture;
And if I Dont set the SelectedItem of the combobox to another culture. My languageBehavior class is not notified.
:
public class LanguageBehavior
{
public static DependencyProperty LanguageProperty =
DependencyProperty.RegisterAttached("Language",
typeof(string),
typeof(LanguageBehavior),
new UIPropertyMetadata(OnLanguageChanged));
public static void SetLanguage(FrameworkElement target, string value)
{
target.SetValue(LanguageProperty, value);
}
public static string GetLanguage(FrameworkElement target)
{
return (string)target.GetValue(LanguageProperty);
}
private static void OnLanguageChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as FrameworkElement;
if (e.NewValue!=null)
element.Language = XmlLanguage.GetLanguage(e.NewValue.ToString());
}
}
I'd expect ComboBox Content to work the same as Button Content.
In my Generic.Xaml i do that :
<Style TargetType="{x:Type TextBlock}" x:Key="_textBlockLanguageProperty">
<Setter Property="WpfServices:LanguageBehavior.Language" Value="{Binding Path=CurrentCulture, Source={x:Static Localized:ResourcesManager.Current}}"
/>
</Style>
And that is CurrentCulture
public CultureInfo CurrentCulture
{
get { return CultureProvider.Current; }
set
{
if (value != CultureProvider.Current)
{
CultureProvider.Current = value;
OnCultureChanged();
}
}
}
Current :
public static ResourcesManager Current
{
get
{
if (_resourcesManager == null)
{
var cultureProvider = new BaseCultureProvider();
_resourcesManager = new ResourcesManager(cultureProvider);
_resourcesManager.Init();
}
return _resourcesManager;
}
}
EDIT :
My _culturedTitelViewModelSelectorStyle is
<Style TargetType="{x:Type ComboBox}" x:Key="_culturedTitleViewModelSelectorStyle">
<Setter Property="DisplayMemberPath" Value="DisplayName" />
<Setter Property="SelectedValuePath" Value="." />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="MaxHeight" Value="40" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Margin" Value="5" />
<Setter Property="SelectedIndex" Value="0" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
In the ComboBox you are binding the SelectedValue to a specific culture. This will select that culture from the list of available cultures, and therefor, trigger a set on the CurrentCulture property.
The Content property of a Button is merely displaying something to the user, it is not doing any assigning. It reads the property value and then displays it. That is why you need to manually change the Culture in the Click event to get it to do anything.
If you want the user to be able to select a value from a list of available values, a ComboBox or ListBox is the way to go. A Button is for triggering a specific action, not for selecting from a list.