c# Wpf mvvm - combo box with select all option - c#

I have datagrid of objects (CopyObject). Each object contains combo box with list of another objects (PGroupGridObject) and a checkbox.
To each PGroupGridObject list in CopyObject i add dummy PGroupGridObject with the name "All".
In PGroupGridObject class i add an event that triggered if the "All" group is checked.
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged("IsChecked");
if (PGroupName.Equals("All"))
{
pGroupGridObjectEvent.pGroupGridObject = this;
MyCustomEvent?.Invoke(this, pGroupGridObjectEvent);
}
}
}
the event in PGroupGridObject:
public delegate void MyEventHandlerPGroup(object sender, MyEventArgsPGroup args);
public class MyEventArgsPGroup : EventArgs
{
public PGroupGridObject pGroupGridObject { get; set; }
}
public class PGroupGridObject : ViewModelBase
{
public string coName;
public event EventHandler<MyEventArgsPGroup> MyCustomEvent;
MyEventArgsPGroup pGroupGridObjectEvent = new MyEventArgsPGroup();
...
...
}
In CopyObject constructor I'm checking this event when add groups to the list.
foreach (PGroupGridObject pGroup in pGroups)
{
PGroupGridObject p = new PGroupGridObject(pGroup.Object);
p.MyCustomEvent += (o, e) =>
{
foreach (PGroupGridObject curpg in pGroups)
curpg.IsChecked = true;
};
this.PGroups.Add(p);
}
When i'm debugging it i can see that at the end of the action the groups IsChecked field is set to true , but i don't see it in the grid. seems that the OnPropertyChanged("IsChecked") not working in that case.
What i'm missing here ?

Related

Access to ObservableCollection

I'm asking how we can acceed to an ObservableCollection.
Actually I'm working on a project and i have to collect the checked elements in an ObservableCollection in order to copy these elements to a PDF file.
public Class FianlElements
{
private int chapAr;
public int ChapAr
{
get { return chapAr; }
set
{
chapAr = value;
OnPropertyChanged("ChaprAr");
OnPropertyChanged("Article");
}
}
private string article;
public string Article
{
get { return article; }
set
{
article = value;
OnPropertyChanged("ChapAr");
OnPropertyChanged("Article");
}
}
private float somme;
public float Somme
{
get { return somme; }
set
{
somme = value;
OnPropertyChanged("Somme");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertySomme)
{
if (PropertyChanged != null)
PropertyChanged(this, new
PropertyChangedEventArgs(propertySomme));
}
}
Actually this is the class of the type of ObservableCollection
My declaration of the ObservableCollection is here
public ObservableCollection<FinalSelection> LesElem { get; set; }
I have another ObservableCollection
public ObservableCollection<ListBoxArticle> LesArticles { get; set; }
This one is Binded to a ListBox which contains CheckBox and TextBox
like this
so I want to copy only the checked elements to "LesElem"
So how can I get access to this ObservableCollection
Thanx
Let's imagine you are interested in LesArticle which are ticked.
AND
You have an IsChecked property on LesArticle.
AND
This is bound to the IsChecked property of the checkbox.
The list of checked ones would be
var checkedList = LesArticle.Where(x => x.IsChecked == true).ToList();
Observablecollection can take a List as it's constructor. So you can do:
FinalElem = new ObservableCollection<ListBoxArticle>(checkedList);

Setting Collection List values from property grid at design time in C#

I have a custom form where I have a GridView on it. Most of my forms will inherit from this custom form.
so let's say that I have
class A : B
{
//Contents
}
with the above scenario, my problem is: I am not able to edit the grid's columns,
on the designer view's property grid. it's like they are locked.
so I have decided to create a custom property to set a list of column names etc.
so to do this I have these classes
[TypeConverter(typeof(BrowseLayoutColumns))]
public class BrowseLayoutColumns : ExpandableObjectConverter
{
#region Properties
private string _columnName = string.Empty;
public string ColumnName
{
get => _columnName;
set
{
if (null == value) return;
_columnName = value;
}
}
private string _bindingField = string.Empty;
public string BindingField
{
get => _bindingField;
set
{
if (null == value) return;
_bindingField = value;
}
}
#endregion
public override string ToString()
{
return "Columns";
}
}
internal class MyList<T> : List<T> where T : class
{
#region ListMethods
public new void Add(T item)
{
base.Add(item);
ListChanged?.Invoke();
}
public new void Clear()
{
base.Clear();
ListChanged?.Invoke();
}
#endregion
#region Events
public event ListChangedEventHandler ListChanged;
public delegate void ListChangedEventHandler();
#endregion
}
and inside my Custom class I added
private MyList<BrowseLayoutColumns> _browseLayoutColumns = new MyList<BrowseLayoutColumns>();
[Category("Design")]
public MyList<BrowseLayoutColumns> BrowseLayoutColumns
{
get => _browseLayoutColumns;
set => _browseLayoutColumns = value;
}
and inside form Initialization I've created the ListChanged event.
private void _browseLayoutColumns_ListChanged()
{
if (_browseLayoutColumns == null) return;
foreach (var column in _browseLayoutColumns)
{
myGridView1.Columns.Add(column.ColumnName, column.BindingField);
}
}
so now as you can see below in the design time I'm able to add columns
the problem here is, it's like the data entered here is not persistent, I mean, it is not adding these values to the columns because my event is not triggered when I run the program and when I debug I see that my BrowseLayoutList property is empty.
any help?
P.S I've tested my event and others by adding to browselayoutcolumns property manually

Custom group box not binding to bindingsource

I need to bind a GroupBox to a BindingSource, which in turn is bound to the following object:
public class CustomerType
{
public int Id {get; set;}
public string Name {get; set;}
public MemberType MemberType {get; set;}
}
public enum MemberType {Adult, Child}
I followed this answer to create a custom GroupBox. I also set the data bindings as follows:
groupBoxMemberType.DataBindings.Add("Selected", this.bindingSource, "MemberType");
However, when loading an existing object, I get the following exception:
DataBinding cannot find a row in the list that is suitable for all bindings.
The exception occurs when setting the data source:
customerType = customerTypeRequest.Load(id);
bindingSource.DataSource = customerType; //raises exception
What am I missing? Is there an alternative to get radio buttons to bind to a datasource, specifically a BindingSource?
This is the changed code:
[DefaultBindingProperty("Selected")]
public class RadioGroupBox : GroupBox
{
#region events
[Description("Occurs when the selected value changes.")]
public event SelectedChangedEventHandler SelectedChanged;
public class SelectedChangedEventArgs : EventArgs
{
public int Selected { get; private set; }
internal SelectedChangedEventArgs(int selected)
{
this.Selected = selected;
}
}
public delegate void SelectedChangedEventHandler(object sender, SelectedChangedEventArgs e);
#endregion
private int selected;
[Browsable(false)]
[Bindable(BindableSupport.Yes, BindingDirection.TwoWay)]
[Description("The selected value associated with this control."), Category("Data")]
public int Selected
{
get { return selected; }
set
{
int val = 0;
var radioButton = this.Controls.OfType<RadioButton>()
.FirstOrDefault(radio =>
radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val) && val == value);
if (radioButton != null)
{
radioButton.Checked = true;
selected = val;
}
}
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
var radioButton = e.Control as RadioButton;
if (radioButton != null)
radioButton.CheckedChanged += radioButton_CheckedChanged;
}
protected void OnSelectedChanged(SelectedChangedEventArgs e)
{
if (SelectedChanged != null)
SelectedChanged(this, e);
}
private void radioButton_CheckedChanged(object sender, EventArgs e)
{
var radio = (RadioButton)sender;
int val = 0;
if (radio.Checked && radio.Tag != null
&& int.TryParse(radio.Tag.ToString(), out val))
{
selected = val;
OnSelectedChanged(new SelectedChangedEventArgs(selected));
}
}
}
Further to setting the Tag property to the corresponding int value of the enum, you need to subscribe to the SelectedChanged event in your form, eg:
private void radioGroupBoxMemberType_SelectedChanged(object sender, SelectedChangedEventArgs e)
{
customerType.MemberType = (MemberType)e.Selected;
}
Improvements to this class would be:
Inherit from RadioButton and use a new property instead of the Tag property.
Access and set the bindingsource property directly in the control to avoid subscribing to the event.

Manipulating collection from the inside

I have a collection of panels which are highlighted when user clicks on them. I want to force them to behave as a set of radio buttons so only the one that is clicked on is highlighted and others aren't.
I guess that there must be a way to manipulate whole collection (set property to false) from the inside, because the event is triggered by one item from the collection. Is there a way for the one item to manipulate whole collection? This is such a common feature in applications so I guess there must be a pattern how to do it properly. Thanks.
You may store collection of your panels and handle required functionality as in following code snippet:
List<Panel> Panels;
private void Initialization()
{
Panels = new List<Panel>();
Panels.Add(pnl1);
Panels.Add(pnl2);
//add all your panels into collection
foreach(Panel Item in this.Panels)
{
//add handle to panel on click event
Item.Click += OnPanelClick;
}
}
private void OnPanelClick(object sender, EventArgs e)
{
foreach(Panel Item in this.Panels)
{
//remove highlight from your panels, real property should have other name than Panel.HighlightEnabled
Item.HighlightEnabled = false;
}
((Panel)sender).HighlightEnabled = true; //add highlight to Panel which invoked Click event
Application.DoEvents(); //ensure that graphics redraw is completed immediately
}
private void AddNewPanelIntoLocalCollection(Panel panel)
{
//here you can add new items to collection during program lifecycle
panel.Click += OnPanelClick;
this.Panels.Add(panel);
}
This is how I do it
public class SelectOne : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private bool isSelected = false;
private HashSet<SelectOne> selecteOnes = null;
public bool IsSelected
{
get { return isSelected; }
set
{
if (isSelected == value) return;
if (isSelected && selecteOnes != null)
{
foreach (SelectOne so in selecteOnes)
{
if (so == this) continue;
so.IsSelected = false;
}
}
NotifyPropertyChanged("IsSelected");
}
}
public SelectOne() { }
public SelectOne(bool IsSelected) { isSelected = IsSelected; }
public SelectedOne(bool IsSelected, HashSet<SelectOne> SelecteOnes)
{
isSelected = IsSelected;
selecteOnes = SelecteOnes;
}
}
Eventually I did find a way to do this properly with only one delegate.
In class A I have a collection of objects B
List<B> b = new List<B>
class B, needs to have an unique ID and delegete for void metod with Id parameter
delegate void DeleteItemDelegate(int id);
class B
{
public int ID {get; set;}
public DeleteItemDeleate deleteThis {set; get;}
}
class A has a metod like this:
public void RemoveItem(int id)
{
for (int x = 0; x < b.Count; x++)
{
if (b[x].id == id)
{
b.RemoveAt(x);
}
}
}
when adding a new B object into List just add metod RemoveItem to B.deleteThis delegate
B bObject = new B();
bObject.deleteThis = RemoveItem;
b.Add(bObject);
Now all you need to do is add DeleteMe metod in B class
void DeleteMe()
{
// and call local delegate - pointing to metod which actually can manipulate the collection
deleteThis(id);
}

DataGrid - change edit behaviour

I have an ObservableCollection of ChildViewModels with somewhat complex behaviour.
When I go to edit a row - the DataGrid goes into 'edit-mode' - this effectively disables UI-notifications outside the current cell until the row is committed - is this intended behaviour and more importantly can it be changed?
Example:
public class ViewModel
{
public ViewModel()
{
Childs = new ObservableCollection<ChildViewModel> {new ChildViewModel()};
}
public ObservableCollection<ChildViewModel> Childs { get; private set; }
}
public class ChildViewModel : INotifyPropertyChanged
{
private string _firstProperty;
public string FirstProperty
{
get { return _firstProperty; }
set
{
_firstProperty = value;
_secondProperty = value;
OnPropetyChanged("FirstProperty");
OnPropetyChanged("SecondProperty");
}
}
private string _secondProperty;
public string SecondProperty
{
get { return _secondProperty; }
set
{
_secondProperty = value;
OnPropetyChanged("SecondProperty");
}
}
private void OnPropetyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
And in View:
<Window.Resources>
<local:ViewModel x:Key="Data"/>
</Window.Resources>
<DataGrid DataContext="{Binding Source={StaticResource Data}}" ItemsSource="{Binding Childs}"/>
Notice how the second notification when editing first column is hidden until you leave the row.
EDIT: Implementing IEditableObject does nothing:
public class ChildViewModel : INotifyPropertyChanged,IEditableObject
{
...
private ChildViewModel _localCopy;
public void BeginEdit()
{
_localCopy = new ChildViewModel {FirstProperty = FirstProperty, SecondProperty = SecondProperty};
}
public void EndEdit()
{
_localCopy = null;
}
public void CancelEdit()
{
SecondProperty = _localCopy.SecondProperty;
FirstProperty = _localCopy.FirstProperty;
}
}
This behavior is implemented in DataGrid using BindingGroup. The DataGrid sets ItemsControl.ItemBindingGroup in order to apply a BindingGroup to every row. It initializes this in MeasureOverride, so you can override MeasureOverride and clear them out:
public class NoBindingGroupGrid
: DataGrid
{
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = base.MeasureOverride(availableSize);
ClearBindingGroup();
return desiredSize;
}
private void ClearBindingGroup()
{
// Clear ItemBindingGroup so it isn't applied to new rows
ItemBindingGroup = null;
// Clear BindingGroup on already created rows
foreach (var item in Items)
{
var row = ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
row.BindingGroup = null;
}
}
}
This is very old question, but a much better solution which doesn't require subclassing DataGrid exists. Just call CommitEdit() in the CellEditEnding event:
bool manualCommit = false;
private void MyDataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (!manualCommit)
{
manualCommit = true;
MyDataGrid.CommitEdit(DataGridEditingUnit.Row, true);
manualCommit = false;
}
}
ok, so, here is the problem. Observable Collection does NOT notify of objects that it contains changing. It only notifies on add/remove/etc. operations that update the collection is-self.
I had this problem and had to manually add my columns to the datagrid, then set the Binding item on the Column object. so that it would bind to my contents.
Also, I made the objects that are in my ICollectionView derive from IEditableObject so when they are "updated" the grid will refresh itself.
this sucks, but its what i had to do to get it to work.
Optionally, you could make your own ObservableCollection that attaches/detaches property changed handlers when an item is addeed and remove.

Categories