How to bind two things to IsChecked property of checkbox? - c#

Ok, so I have a datagrid with a checkbox in a DataGridTemplateColumn since I would like to do a single-click check. I also have a "select all" checkbox at the top. (I disabled the header because I wanted to make a custom one). My datagrid is programmatically populated. and I'm using the mahApps nuget package (if that means anything).
What I want to do is bind the "Select All" status to the IsChecked property on my populated checkboxes but also be able to click the row to check the checkbox. But to be able to do that, I need to add: IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected, Mode=OneWay}".
I have both of them, and they both work, but I can only seem to use one at a time. How would I go about doing both? Please let me know if you need more info
Edit: I would also need multi-selection!

This is psuedo code:
To make each rows checkbox clickable independently - the ViewModel for row should have bool IsChecked get set property. For Top level "Select All" checkbox - you will need a top level bool? SelectAll get set property too - getter will be something like --
Lets says collection bound to GridView is
List<RowViewModel>RowViewModelCollection;
Then each RowViewModel object will have:
private bool _IsSelected = null;
public bool IsSelected
{
get => _IsSelected;
set
{
_IsSelected = value;
// NotifyProperty Change for this property
// Notify Property Change for TopLevel SelectAll property
}
}
Your Top level SelectAll should look like:
public bool? SelectAll
{
get
{
if(RowViewModelCollection.All(r=>r.IsSelected == true))
return true;
else if(RowViewModelCollection.All(r=>r.IsSelected == false))
return false;
else
return null;
}
set
{
foreach(var row in RowViewModelCollection)
{
row.IsSelected = value;
}
// Notify Property Changed for This property
}
}

Related

WPF ListView select/deselect under some conditions

I'm using a WPF ListView to show and select/deselect some items but there are some conditions that user cannot select/deselect an item and I should handle it in the code behind.
I tried to use ListView.SelectionChanged event to handle it but the problem is that when I change the selected item in the code behind (select it again if it was deselected or the other way), the event triggers again and I don't want that.
What is the best way to set conditions on select/deselect ListView item?
I tried to solve it using ListView.PreviewMouseLeftButtonDown event instead of ListView.SelectionChanged. But I wanted to know if there is a better way?
There might be other WPF controls (especially third party ones) that can handle this a little more gracefully. Additionally you can hook into the style of the ListView to alter the mouse-over behavior.
As a crude approach; however, I was able to accomplish what you're looking for by using the SelectionChanged event to effectively "undo" any selections we deem invalid.
First a simple ListView in xaml:
<ListView
x:Name="ItemsLv"
SelectionChanged="ItemsLv_SelectionChanged"
SelectionMode="Single" />
The SelectionMode being Single is important here as the approach for undoing a selection when multiple items are selected is more complicated.
Then, an object to represent our list items with the ToString() overload implemented so we don't have to fiddle with data templates and binding.
public class MyListViewItem
{
public string Text { get; set; }
public bool IsSelectable { get; set; }
public override string ToString()
{
return Text;
}
}
This object just represents a string (our data) paired with a boolean of whether or not we want this item to be selectable.
Then, a quick little setup to populate the list with a few item for testing:
public MainWindow()
{
InitializeComponent();
var items = new List<MyListViewItem>
{
new() { Text = "Item One", IsSelectable = true },
new() { Text = "Item Two", IsSelectable = true },
new() { Text = "Item Three", IsSelectable = false },
new() { Text = "Item Four", IsSelectable = true }
};
ItemsLv.ItemsSource = items;
}
And finally, the "magic". The SelectionChanged event handler:
private void ItemsLv_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// if this event wasn't caused by a new item that wasn't already
// selected being selected, don't do anything extra
if (e.AddedItems.Count <= 0)
{
return;
}
// get the newly selected item and cast it to a MyListViewItem so we can inspect the IsSelectable property
var selectedItem = (MyListViewItem)ItemsLv.SelectedItem;
// if it's a selectable item, we don't need to intervene
if (selectedItem.IsSelectable)
{
return;
}
// we made it this far that means we tried to select an item that should NOT be selectable
// if the new selected item caused us to UNselect an old item, put the selection back
if (e.RemovedItems.Count > 0)
{
ItemsLv.SelectedItem = e.RemovedItems[0];
}
// otherwise (the first selection ever?) just set selection back to null
else
{
ItemsLv.SelectedItem = null;
}
}
Hopefully the code comments in there make it clear what's going on.

Using c# / WPF / livecharts. How can I set visibility of individual item at SeriesCollection?

I'm using SeriesCollection for my chart series.
What I want to do is toggle visibility when user check/uncheck at series list.
At XAML, ItemsControl code is like this.
<ItemsControl ItemsSource="{Binding Path=SeriesItemList}"
ItemTemplate="{StaticResource toggleChartItemTemplate}"
>
At item template, checkbox IsChecked event is bound like this.
<CheckBox IsChecked="{Binding Path=IsChecked}" >
</CheckBox>
And when IsChecked is called, it is written at class file as
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
Console.Write("");
mainViewModel.ToggleSeries(SeriesName, value);
RaisePropertyChanged("IsChecked");
}
}
And at last, ToggleSeries function called
public void ToggleSeries(string SeriesName, bool value)
{
for (int idx = 0; idx < MainChartSeries.Count; idx++)
{
if (MainChartSeries[idx].Title == SeriesName)
{
//We will set visibility of the series here.
}
}
Console.Write("");
}
The codes work without error. So I think there is no probelm.
If I can Find out How can I set visibility of the individual series visibility
of SeriesCollection.
What can I access is IsSeriesVisible property. it doesn't have set method.
How can I set it's visibility? Should I change SeriesCollection to something else?
Thank you.
Just had the same Problem and found a solution.
you can cast your SeriesCollection[idx] to the kind of Series you are using
like so:
LineSeries seriesToHide = (MainChartSeries[idx] as LineSeries);
seriesToHide.Visibility = Visibility.Collapsed;
And there you have your Visibility property with a setter, and even more other usefull properties.

Update ObservableCollection without blinking

Could someone help me, how can I update ObservableCollection, which is binded to ListView ItemSource, without blinking? When I do:
Contacs = _contacs;
the whole ListView is blinking. I would like to search in ListView too, but always after replacing the old results with new one, the listview blinks.
The problem here is, that you are reassigning the whole collection. This does not take advantage of the observability and forces the ListView to reload all items. Try to remove/add the items instead so the ListView only needs to update the Items that actually changed.
In the case of searching hiding the unmatched results might be a viable solution too. To do that create a property on your Contact type (called "IsVisible" for example) and bind it to the ListViewItems Visibility Property. (You might need the build in BooleanToVisibility Converter here)
Update
As pointed out in the comments using a CollectionViewSource is the correct wpf way of implementing a search filter. See this answer for details on how to use it.
If you want to enable filtering in your collection then there is no actual need to perform operations directly on your collection.
Use ICollectionView and CollectionViewSource for this purpose.
As you have an ObservableCollection so you can do something like this.
ICollectionView contactsView;
public ICollectionView ContactsView
{
get { return contactsView; }
set
{
if(contactsView != value)
{
contactsView = value;
}
}
}
And in the setter of the ObservableCollection
public ObservableCollection<ContactType> Contacs
{
get { return _contacs; }
set
{
if(_contacs != value)
{
_contacs = value;
ContactsView = CollectionViewSource.GetDefaultView(value);
ContactsView.Filter = ContactsFilter;
}
}
}
where ContactsFilter is a function with following definition.
bool ContactsFilter(object item)
{
var contact = item as ContactType;
if(condition)
return true; //show this item in ListView.
return false; //Do not show this item in ListView
}
and whenever you want to filter items you can do that just by
ContactsView.Refresh();
which I think will be in the TextChanged Event of your text box in which you are entering search query.
More detailed article is at CollectionViewSource

WPF Binding to behave OneWayToSource and OneTime

Is it possible for a control binding to behave as OneWayToSource and OneTime?
Here is some background: I have a data grid. Each row has text cells and a checkbox. If the checkbox is selected the data from the row will be saved. Now, when user starts typing in any text cell I change the IsChecked property in my ViewModel so the checkbox gets checked. I would like this to happen only once. So, for example, if a user starts typing and decides to uncheck the checkbox I don't want to change it when the user will start typing value again.
Sounds for me like setting binding to both OneWayToSource and OneTime should be a solution but I know binding mode can be set only to a single value. I've been searching for some suggestions and possible workarounds to achieve similar result but with no result.
From msdn:
OneTime updates the target property only when the application starts
or when the DataContext undergoes a change
That means that using a combination of OneWayToSource and OneTime would not solve your issue as the 'one time'-update does not trigger the moment the property changes the first time but on application start or datacontext change.
As you bound your text cell text to some property you can control in that property if IsChecked should be set or not.
private string text_ = "";
private bool isChecked_ = false;
private bool autoSetChecked_ = true;
public bool IsChecked
{
get
{
return isChecked_;
}
set
{
if (isChecked_ == value) {
return;
}
// If user manually changes check state assume the user wants to keep that state
// => Disable auto changing.
autoSetChecked_ = false;
isChecked_ = value;
OnPropertyChanged("IsChecked");
}
}
public Text
{
get
{
return text_;
}
set
{
if (text_ == value) {
return;
}
text_ = value;
OnPropertyChanged("Text");
if (autoSetChecked_) {
// Only set is checked if not done ever before.
autoSetChecked_ = false;
IsChecked = true;
}
}
}
Edit: This requires that your IsChecked-Binding is TwoWay so you can change the checkbox from your viewmodel.

C# grid binding not update

I have a grid that is binded to a collection. For some reason that I do not know, now when I do some action in the grid, the grid doesn't update.
Situation : When I click a button in the grid, it increase a value that is in the same line. When I click, I can debug and see the value increment but the value doesn't change in the grid. BUT when I click the button, minimize and restore the windows, the value are updated... what do I have to do to have the value updated like it was before?
UPDATE
This is NOT SOLVED but I accepted the best answer around here.
It's not solved because it works as usuall when the data is from the database but not from the cache. Objects are serialized and threw the process the event are lost. This is why I build them back and it works for what I know because I can interact with them BUT it seem that it doesn't work for the update of the grid for an unkown reason.
In order for the binding to be bidirectional, from control to datasource and from datasource to control the datasource must implement property changing notification events, in one of the 2 possible ways:
Implement the INotifyPropertyChanged interface, and raise the event when the properties change :
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
NotifyPropertyChanged("Name");
}
}
}
Inplement a changed event for every property that must notify the controls when it changes. The event name must be in the form PropertyNameChanged :
public event EventHandler NameChanged;
public string Name
{
get
{
return this._Name;
}
set
{
if (value != this._Name)
{
this._Name= value;
if (NameChanged != null) NameChanged(this, EventArgs.Empty);
}
}
}
*as a note your property values are the correct ones after window maximize, because the control rereads the values from the datasource.
It sounds like you need to call DataBind in your update code.
I am using the BindingSource object between my Collection and my Grid. Usually I do not have to call anything.

Categories

Resources