I' ve got an error when I attempt to update integer parameter in my database. If I changed it +1/-1 everything fine but changing it more (+5, +7, -4) save +1/-1 step only (example: change 5 to 10 and load it from DB - get 6).
Objects of drug_storage class are in a BindingList. I trace changes using ListChanged method. I use integer updown counter binded to drug_quantity parameter in two way mode; also use not mapped integer tag property of an object to store information about previous number of parameter. If counter change this parameter tag != parameter and DB updates
XAML:
<UserControl.Resources>
<DataTemplate x:Key="ComboBoxDataTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding is_selected, Mode=TwoWay}" x:Name="checkbox" Margin="3" Checked="SelectComboBoxItem" Unchecked="SelectComboBoxItem" />
<TextBlock Text="{Binding public_drug_name}" Margin="2" />
<lc1:IntegerUpDown x:Name="iudQuantity" Minimum="1" Increment="1" Value="{Binding drug_quantity, Mode=TwoWay}" Margin="3"
Visibility="{Binding IsChecked, ElementName=checkbox, Converter={StaticResource BooleanToVisibilityConverter}}" ValueChanged="SelectComboBoxItem" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<ComboBox ItemsSource="{Binding InfusionDrugList}" Margin="5" ItemTemplate="{StaticResource ComboBoxDataTemplate}"
SelectedItem="{Binding SelectedDrug, Mode=OneWayToSource}">
XAML.CS (if something in data template is clicked comboboxitem becomes selected and set value of SelectedDrug what triggers viewmodel code:
private void SelectComboBoxItem(object sender, RoutedEventArgs e)
{
var v = FindVisualParent<ComboBoxItem>(sender as UIElement);
if (v != null) { v.IsSelected = true; } // some checkboxes can become selected automatically at the moment of druglist loading and v would be null
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element; while (parent != null)
{
T correctlyTyped = parent as T; if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}
private void SelectComboBoxItem(object sender, RoutedPropertyChangedEventArgs<int> e)
{
var v = FindVisualParent<ComboBoxItem>(sender as UIElement);
if (v != null) { v.IsSelected = true; } // some updowns can become selected automatically at the moment of druglist loading and v would be null
}
VIEW MODEL:
In Constructor:
SelectedDrugs = new BindingList<drug_storage>();
SelectedDrugs.RaiseListChangedEvents = true;
SelectedDrugs.ListChanged += ProcessSelectedDrugCommands;
Later:
` public BindingList<drug_storage> SelectedDrugs { get; set; }
private drug_storage selectedDrug;
public drug_storage SelectedDrug
{
get { return selectedDrug; }
set
{
selectedDrug = value;
NotifyPropertyChanged();
AddDrugForProcessing();
}
}
private void AddDrugForProcessing()
{
if (SelectedDrug != null && !SelectedDrugs.Contains(SelectedDrug))
{
SelectedDrugs.Add(SelectedDrug);
}
}
private void ProcessSelectedDrugCommands(object sender, ListChangedEventArgs e)
{
.... //code to add and remove objects to DB
if (e.ListChangedType == ListChangedType.ItemChanged)
{
int position = e.NewIndex;
drug_storage drug = SelectedDrugs[position];
if (drug.is_selected && drug.tag != drug.drug_quantity) //when user changed number in updown counter
{
ChangeNumberOfDrugInDB(drug);
}
void ChangeNumberOfDrugInDB (drug_storage drug)
{
int procedure_id = SelectedInListProcedure.PMR_Id;
int drug_id = SelectedDrug.drug_id;
using (var db = new MainEntityModel())
{
var query = (from d in db.PMR_infusions_drugs
where d.PMR_Id == procedure_id && d.drug_id == drug_id
select d).Single();
query.drug_quantity = drug.drug_quantity;
db.SaveChanges();
drug.tag = drug.drug_quantity;
}
}}
I believe every time user change integerupdown binded to drug_quantity parameter it triggers DB update and then updates tag property but it looks like after inintial 1-integer-step updating drug.tag already becomes equal to final drug_quantity value. Can it be so?
Thanks
ADDITIONALY:
EF Class:
public partial class drug_storage
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public drug_storage()
{
PMR_infusions_drugs = new HashSet<PMR_infusions_drugs>();
}
[Key]
public int drug_id { get; set; }
[Required]
[StringLength(50)]
public string drug_name { get; set; }
[NotMapped]
public bool is_selected { get; set; }
[NotMapped]
private int quantity = 1;
[NotMapped]
public int drug_quantity { get { return quantity; } set { quantity = value; } }
[NotMapped]
public int tag = 0;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<PMR_infusions_drugs> PMR_infusions_drugs { get; set; }
}
Related
My scenario: I have a usercontrol consisting of a comboBox, and a TextBox. The comboBox should hold numbers contained in an ObservableCollection.
The task: The numbers in the ObservableCollection represent paths to book-chapters; therefore each chapter is unique. Meaning: if I have chapters 1 - 5, then the first userControl combo should show all chapters 1-5 (whereas one of them is selected randomly), the second userControl combo contains all chapters, but not the one selected in the previous combo, and so on. The textBox is for annotations for the chapters.
What I achieved so far: I have currently no model; just a main viewModel (ItemsViewModel in my case), and a viewModel for my userControl (PathViewModel). Then there is the mainWindow view.
The problem: On my mainWindow I can create several dynamically created userControls. The userControl TextBox is currently bound to a text property, while the index of the comboBox is bound to another property. But I don't know:
- how to gain access to the index, selected item/value of the specifically userControls
- how to react to a comboBox item/index change
Here is my code:
The userControl
<UserControl> <StackPanel Orientation="Horizontal">
<ComboBox x:Name="combo" Margin="10" MinWidth="60" VerticalAlignment="Center" ItemsSource="{Binding AvailableNumbers}" SelectedIndex="{Binding TheIndex}" />
<TextBox Margin="10" MinWidth="120" Text="{Binding TheText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
The MainWindow
<Window>...<Window.DataContext>
<local:ItemsViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="HostPanel">
<ItemsControl ItemsSource="{Binding PathViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PathControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel Grid.Column="1">
<Button Command="{Binding UCCreationCommand}" Content="Add User Control" Margin="10"/>
<Button Command="{Binding UCDeletionCommand}" CommandParameter="" Content="Delete User Control" Margin="10"/>
<Button Command="{Binding ReadoutCommand}" Content="Show ITEMS" Margin="10"/>
</StackPanel>
</Grid></Window>
My main ViewModel (called ItemsViewModel)
public class ItemsViewModel : NotifyPropertyChangedBase
{private int _aNumber;
public int ANumber
{
get { return _aNumber; }
set { _aNumber = value;
OnPropertyChanged(ref _aNumber, value);
}
}
public ObservableCollection<PathViewModel> PathViewModels { get; set; } = new
ObservableCollection<PathViewModel>();
public ObservableCollection<int> AllNumbers { get; set; } = new ObservableCollection<int>();
public ItemsViewModel()
{
UCCreationCommand = new CommandDelegateBase(UCCreationExecute, UCCreationCanExecute);
UCDeletionCommand = new CommandDelegateBase(UCDeletionExecute, UCDeletionCanExecute);
ReadoutCommand = new CommandDelegateBase(ReadoutExecute, ReadoutCanExecute);
AllNumbers.Add(1);
AllNumbers.Add(2);
AllNumbers.Add(3);
AllNumbers.Add(4);
AllNumbers.Add(5);
}
private bool ReadoutCanExecute(object paramerter)
{
if (PathViewModels.Count > 0)
{
return true;
}
return false;
}
private void ReadoutExecute(object parameter)
{
//just for testing
}
public ICommand UCCreationCommand { get; set; }
public ICommand UCDeletionCommand { get; set; }
public ICommand ReadoutCommand { get; set; }
private bool UCCreationCanExecute(object paramerter)
{
if (PathViewModels.Count < 8)
{
return true;
}
else
{
return false;
}
}
private void UCCreationExecute(object parameter)
{
PathViewModel p = new PathViewModel();
foreach (int i in AllNumbers)
{
p.AvailableNumbers.Add(i);
}
int rndIndex = 0;
Random rnd = new Random();
//creates a random chapter index
rndIndex = rnd.Next(0, p.AvailableNumbers.Count);
//just explicit for debugging reasons
p.TheIndex = rndIndex;
AllNumbers.RemoveAt(rndIndex);
PathViewModels.Add(p);
}
private bool UCDeletionCanExecute(object paramerter)
{
if (PathViewModels.Count != 0)
{
return true;
}
else
{
return false;
}
}
private void UCDeletionExecute(object parameter)
{
PathViewModel p = new PathViewModel();
int delIndex = PathViewModels.Count - 1;
p = PathViewModels[delIndex];
AllNumbers.Add((int)p.TheValue+1);
PathViewModels.Remove(p);
}
}
And finally my UserControl ViewModel:
public class PathViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<int> AvailableNumbers { get; set; } = new ObservableCollection<int>();
private int _theIndex;
public int TheIndex
{
get { return _theIndex; }
set
{
_theIndex = value;
OnPropertyChanged(ref _theIndex, value);
}
}
private int _theValue;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged(ref _theValue, value);
}
}
private string _theText;
public string TheText
{
get { return _theText; }
set
{
_theText = value;
OnPropertyChanged(ref _theText, value);
}
}
public PathViewModel()
{
}
}
Any hints on how to go on from here would be highly appreaciated.
The 2nd column items "Point Setting" don't show up until the 1st column items are sorted, clicking on the header of the 1st column. The goal of this code is to link the 1st and 2nd column items, then use the 2nd column items as the search keys.
I'm new to C# and WPF.
I tired to put sequential numbers in front of the 1st column items (1., 2., and so on) because I thought it would solve the problem if those items are initially sorted. But, no luck. I heard that ObservableCollection<> doesn't manage the input order, so once I changed it with List<>. But it didn't solve this problem, too.
Actually, I don't want to sort the 1st column; they should be fixed and no need to change the order/number at all.
To avoid any confusions, let me show my entire codes (sorry).
MainWindow.xaml:
<Window x:Class="XY.MainWindow"
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:XY"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="454.4">
<Grid>
<DataGrid ItemsSource="{Binding channels}"
SelectedItem="{Binding SelectedRow, Mode=TwoWay}"
Margin="0,0,0,-0.2">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding name}"
Header="Channel" Width="Auto"/>
<DataGridTemplateColumn Width="100" Header="Point Setting">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="piontsComboBox"
ItemsSource="{Binding DataContext.points,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectionChanged="PrintText"
DisplayMemberPath="name"
SelectedValuePath="name"
Margin="5"
SelectedItem="{Binding DataContext.SelectedPoint,
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBox x:Name="tb" Width="140" Height="30" Margin="10,250,200,30"></TextBox>
<Button x:Name="Browse_Button" Content="Browse" Margin="169,255,129.6,0"
Width="75" Click="Browse_Button_Click" Height="30" VerticalAlignment="Top"/>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace XY
{
public partial class MainWindow : Window
{
public GridModel gridModel { get; set; }
public MainWindow()
{
InitializeComponent();
gridModel = new GridModel();
this.DataContext = gridModel;
}
private void Browse_Button_Click(object sender, RoutedEventArgs e)
{
WakeupClass clsWakeup = new WakeupClass();
clsWakeup.BrowseFile += new EventHandler(gridModel.ExcelFileOpen);
clsWakeup.Start();
}
void PrintText(object sender, SelectionChangedEventArgs args)
{
var comboBox = sender as ComboBox;
var selectedPoint = comboBox.SelectedItem as Point;
tb.Text = selectedPoint.name;
}
}
public class WakeupClass
{
public event EventHandler BrowseFile;
public void Start()
{
BrowseFile(this, EventArgs.Empty);
}
}
}
ViewModelBase.cs:
using System.ComponentModel;
namespace XY
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
Point.cs:
namespace XY
{
public class Point : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _code;
public int code
{
get { return _code; }
set
{
_code = value;
OnPropertyChanged("code");
}
}
}
}
Record.cs:
namespace XY
{
public class Record : ViewModelBase
{
private string _name;
public string name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("name");
}
}
private int _PointCode;
public int PointCode
{
get { return _PointCode; }
set
{
_PointCode = value;
OnPropertyChanged("PointCode");
}
}
private Record _selectedRow;
public Record selectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
OnPropertyChanged("SelectedRow");
}
}
private Point _selectedPoint;
public Point SelectedPoint
{
get { return _selectedPoint; }
set
{
_selectedPoint = value;
_selectedRow.PointCode = _selectedPoint.code;
OnPropertyChanged("SelectedRow");
}
}
}
}
GridModel.cs:
using System.Collections.ObjectModel;
using System.Windows;
namespace XY
{
public class GridModel : ViewModelBase
{
public ObservableCollection<Record> channels { get; set; }
public ObservableCollection<Point> points { get; set; }
public GridModel()
{
channels = new ObservableCollection<Record>() {
new Record {name = "1. High"},
new Record {name = "2. Middle"},
new Record {name = "3. Low"}
};
}
internal void ExcelFileOpen(object sender, System.EventArgs e)
{
points = new ObservableCollection<Point> { new Point { } };
MessageBox.Show("Please assume that Excel data are loaded here.");
points.Add(new Point { name = "point1", code = 1 });
points.Add(new Point { name = "point2", code = 2 });
points.Add(new Point { name = "point3", code = 3 });
points.Add(new Point { name = "point4", code = 4 });
}
}
}
The procedure goes like:
Click on the "Browse" button to load the data.
Click on the 1st column "Channel" to sort the list (GOAL: I'd like to GET RID OF this step).
Click on the "Point Setting" ComboBox to select the items (point1, point2, ..., etc.).
... I don't know if ObservableCollection<> is appropriate here. If List<> or any other type is better, please change it. Any suggestion would be helpful. Thank you in advance.
Change your points ObservableCollection like such, because you're setting the reference of the collection after the UI is rendered, you would need to trigger the PropertyChanged event to update the UI.
private ObservableCollection<Point> _points;
public ObservableCollection<Point> points
{
get { return _points; }
set
{
_points = value;
OnPropertyChanged(nameof(points));
}
}
An alternative would be to first initialise your collection.
public ObservableCollection<Point> points { get; set; } = new ObservableCollection<Point>();
internal void ExcelFileOpen(object sender, System.EventArgs e)
{
// Do not re-initialise the collection anymore.
//points = new ObservableCollection<Point> { new Point { } };
points.Add(new Point { name = "point1", code = 1 });
points.Add(new Point { name = "point2", code = 2 });
points.Add(new Point { name = "point3", code = 3 });
}
I have two different objects that are pointing at each other. The first object represents a division in a company. That object has two collection: Employees, which is all the employees working in the division and Project, which is all the special projects that are in progress within that division. So the first object looks like this:
public class Division : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
ObservableCollection<Employee> _employees;
ObservableCollection<Project> _projects;
public Division()
{
Employees = new ObservableCollection<Employee>();
Projects = new ObservableCollection<Project>();
}
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set
{
if (_employees != value)
{
_employees = value;
PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}
}
}
public ObservableCollection<Project> Projects
{
get { return _projects; }
set
{
if (_projects != value)
{
_projects = value;
PropertyChanged(this, new PropertyChangedEventArgs("Projects"));
}
}
}
public void AddNewProject()
{
this.Projects.Add(new Project(this));
}
}
Notice that when adding a new project to the division, I pass a reference to the division into that project, which looks like this:
public class Project : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
string _projectName;
DateTime _deadline = DateTime.Now;
Division _division;
ObservableCollection<Employee> _members;
public Project()
{
Members = new ObservableCollection<Employee>();
}
public Project(Division div)
{
Members = new ObservableCollection<Employee>();
Division = div;
}
public string ProjectName
{
get { return _projectName; }
set
{
if (_projectName != value)
{
_projectName = value;
PropertyChanged(this, new PropertyChangedEventArgs("ProjectName"));
}
}
}
public DateTime Deadline
{
get { return _deadline; }
set
{
if (_deadline != value)
{
_deadline = value;
PropertyChanged(this, new PropertyChangedEventArgs("Deadline"));
}
}
}
public Division Division
{
get { return _division; }
set
{
if (_division != value)
{
if (_division != null)
{
_division.Employees.CollectionChanged -= members_CollectionChanged;
}
_division = value;
if (_division != null)
{
_division.Employees.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Division"));
}
}
}
public ObservableCollection<Employee> Members
{
get { return _members; }
set
{
if (_members != value)
{
if (_members != null)
{
_members.CollectionChanged -= members_CollectionChanged;
}
_members = value;
if (_members != null)
{
_members.CollectionChanged += members_CollectionChanged;
}
PropertyChanged(this, new PropertyChangedEventArgs("Members"));
}
}
}
public ObservableCollection<Employee> AvailableEmployees
{
get
{
if (Division != null){
IEnumerable<Employee> availables =
from s in Division.Employees
where !Members.Contains(s)
select s;
return new ObservableCollection<Employee>(availables);
}
return new ObservableCollection<Employee>();
}
}
void members_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableEmployees"));
}
}
The reason I'm doing it like this is, that the project could have any type of team working on it, but only from within the division. So, when building a dashboard for the division, the manager could select any of the employees to that project but without putting in an employee that is already assigned to it. So, the AvailableEmployees property in the project object always keeps track of who is not already assigned to that project.
The problem I'm having is how to translate this into a UI. The experiment I've done so far looks like this:
<UserControl x:Class="Test.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:local="clr-namespace:Test.Views"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<ListBox ItemsSource="{Binding Div.Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="Transparent"
BorderThickness="0, 0, 0, 2"
BorderBrush="Black"
Margin="0, 0, 0, 5"
Padding="0, 0, 0, 5">
<StackPanel>
<TextBox Text="{Binding ProjectName}"/>
<ListBox ItemsSource="{Binding Members}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add Employee to Project"
Command="{Binding RelativeSource={RelativeSource AncestorType=local:TestView}, Path=DataContext.AddEmployeeToProject}"
CommandParameter="{Binding}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add New Project"
Command="{Binding AddNewProject}" />
</StackPanel>
The view model associated with this view is as follows:
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private Division _div;
public TestViewModel(Division div)
{
Div = div;
AddNewProject = new DelegateCommand(OnAddNewProject);
AddEmployeeToProject = new DelegateCommand<Project>(OnAddEmployeeToProject);
}
public DelegateCommand AddNewProject { get; set; }
public DelegateCommand<Project> AddEmployeeToProject { get; set; }
public Division Div
{
get { return _div; }
set
{
if (_div != value)
{
_div = value;
PropertyChanged(this, new PropertyChangedEventArgs("Div"));
}
}
}
private void OnAddNewProject()
{
Div.AddNewProject();
}
private void OnAddEmployeeToProject(Project proj)
{
var availables = proj.AvailableEmployees;
if (availables.Count > 0)
{
proj.Members.Add(availables[0]);
}
}
}
However, I cannot get the combobox for each employee in each project to work. It seems like the selected item/value is bound to the itemssource, and each time the combobox turns out blank. I've tried to do this also with SelectedValue and SelectedItem properties for the combobox, but none worked.
How do I get these two separated. Is there anything else I'm missing here?
OK. After so many experiments the best solution I came up with was to create my own user control that is composed of both a button and a combobox that imitate the behavior I was expecting of the combobox on it own.
First, I had a really stupid mistake in the model where both lists of members Project and Division contain the same instances of Employee, which makes the AvailableEmployees property buggy. What I really needed to do is to create a list of copies of employees in the Project instead of just references.
In any case, I created a new user control and called it DynamicSourceComboBox. The XAML of this control looks like this:
<Grid>
<Button x:Name="selected"
Content="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=SelectedValue}"
Click="selected_Click"/>
<ComboBox x:Name="selections"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=ItemsSource}"
DisplayMemberPath="{Binding RelativeSource={RelativeSource AncestorType=local:DynamicSourceComboBox}, Path=DisplayMemberPath}"
Visibility="Collapsed"
SelectionChanged="selections_SelectionChanged"
MouseLeave="selections_MouseLeave"/>
</Grid>
I have here a few bindings from the button and the combobox to properties in my user control. These are actually dependency properties. The code-behind of my user control looks like this:
public partial class DynamicSourceComboBox : UserControl
{
public DynamicSourceComboBox()
{
InitializeComponent();
}
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(DynamicSourceComboBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
ComboBox.ItemsSourceProperty.AddOwner(typeof(DynamicSourceComboBox));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty =
ComboBox.DisplayMemberPathProperty.AddOwner(typeof(DynamicSourceComboBox));
private void selected_Click(object sender, RoutedEventArgs e)
{
selected.Visibility = Visibility.Hidden;
selections.Visibility = Visibility.Visible;
selections.IsDropDownOpen = true;
}
private void selections_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
if (e.AddedItems.Count == 1)
{
var item = e.AddedItems[0];
Type itemType = item.GetType();
var itemTypeProps = itemType.GetProperties();
var realValue = (from prop in itemTypeProps
where prop.Name == DisplayMemberPath
select prop.GetValue(selections.SelectedValue)).First();
SelectedValue = realValue;
}
}
private void selections_MouseLeave(object sender, MouseEventArgs e)
{
selections.Visibility = Visibility.Collapsed;
selected.Visibility = Visibility.Visible;
selections.IsDropDownOpen = false;
}
}
These dependency properties imitate the properties with similar names in ComboBox but they are hooked up to the internal combobox and the button in a way that makes them behave together as a single complex combobox.
The Click event in the button hides it and present the combobox to make the effect of just a box that is opening. Then I have a SelectionChanged event in the combobox firing to update all the needed information and a MouseLeave event just in case the user doesn't make any real selection change.
When I need to use the new user control, I set it up like this:
<local:DynamicSourceComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorLevel=1, AncestorType=ListBox}, Path=DataContext.AvailableEmployees}"
DisplayMemberPath="FirstName"
SelectedValue="{Binding FirstName, Mode=TwoWay}"/>
Of course, for all of it to work, I have to make a lot of hookups with PropertyChanged events in the models, so the Projects instance will know to raise a PropertyChanged event for AvailableEmployees any time a change is made, but this is not really the concern of this user control itself.
This is a pretty clunky solution, with a lot of extra code that is a bit hard to follow, but it's really the best (actually only) solution I could have come up with to the problem I had.
My WPF DataGrid is:
<dg:DataGrid Name="datagrid1" Grid.RowSpan="1" VerticalAlignment="Stretch" Grid.ColumnSpan="2">
<dg:DataGrid.Columns >
<dg:DataGridTemplateColumn>
<dg:DataGridTemplateColumn.Header>
<CheckBox Content=" Slect All" x:Name="headerCheckBox" />
</dg:DataGridTemplateColumn.Header>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" Margin="45 2 0 0"
IsChecked="{Binding IsChecked, ElementName=headerCheckBox,
Mode=OneWay}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
</dg:DataGrid.Columns>
</dg:DataGrid>
Also Dynamicaly I am populating the data to the datgrid.In xaml.cs file I written the below given code for deleting the selected row from the data grid but it throwing the error at line
DataGridRow item =(DataGridRow) datagrid1.ItemContainerGenerator.ContainerFromItem(datagrid1.Items[j]);
So Please have a look in to the below given code which I written for doing the same.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
for (int j = 0; j < datagrid1.Items.Count; j++)
{
DataGridRow item =(DataGridRow) datagrid1.ItemContainerGenerator.ContainerFromItem(datagrid1.Items[j]);
CheckBox ckb = (CheckBox)GetVisualChild<CheckBox>(item);
if (ckb.IsChecked.Value)
{
DataRowView drv = (DataRowView)datagrid1.Items[j];
//delete the row- updating to the database
}
}
}
static T GetVisualChild<T>(Visual parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
Please let me know if am wrong.
Here is how I would do this. Implement an ObservableCollection of your class that inherits INotifyPropertyChanged. INotifyPropertyChanged will be used in case we want to update items in the collection.
First the xaml for the GridView
<DataGrid x:Name="gvMain" AutoGenerateColumns="True" HorizontalAlignment="Left"
VerticalAlignment="Top" Height="300" Width="300"></DataGrid>
Our class of items
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string firstName { get; set; }
private string lastName { get; set; }
public string FirstName
{
get
{
return firstName;
}
set
{
firstName = value;
PropertyChangedEvent("FirstName");
}
}
public string LastName
{
get
{
return lastName;
}
set
{
lastName = value;
PropertyChangedEvent("LastName");
}
}
private void PropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Next the WPF window
public ObservableCollection<MyClass> gridData { get; set; }
public MainWindow()
{
InitializeComponent();
gridData = new ObservableCollection<MyClass>();
gvMain.ItemsSource = gridData;
}
Test to add, change, delete items in the collection
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
gridData.Add(new MyClass() { FirstName = "John", LastName = "Smith" });
}
private void btnChange_Click(object sender, RoutedEventArgs e)
{
gridData[0].FirstName = "Meow Mix";
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
//using List to use .ForEach less code to write and looks cleaner to me
List<MyClass> remove = gridData.Where(x => x.LastName.Equals("Smith")).ToList();
remove.ForEach(x => gridData.Remove(x));
}
Any changes you want to make will be done with gridData.
My code is as below.
<ListBox x:Name="lstBoxMarket" BorderThickness="0" Height="Auto" HorizontalAlignment="Center" Width="200" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox IsChecked="{Binding Checked}" CommandParameter="{Binding MarketId}" Tag="{Binding MarketId}" Content="{Binding Market}" Foreground="#FF3D66BE" Name="chkMarket"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to access the selected and deselected checkboxes in the list on click of save button . I am unable to access chkMarket straight away. Can anyone help?
Starting from your code I tried something like that
// find all T in the VisualTree
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject parent)
where T : DependencyObject
{
List<T> foundChilds = new List<T>();
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foreach(var other in FindVisualChildren<T>(child))
yield return other;
}
else
{
yield return (T)child;
}
}
}
Then in your MainWindow
private void button1_Click(object sender, RoutedEventArgs e)
{
// find all checkboxes in my window
IEnumerable<CheckBox> myBoxes = FindVisualChildren<CheckBox>(this);
int numChecked = 0;
foreach(CheckBox cb in myBoxes)
{
if(cb.Name != "chkMarket")
continue;
if (cb.IsChecked == true)
numChecked++;
}
MessageBox.Show("Checked items = " + numChecked);
}
My viewmodel code is
public class ViewModel
{
public ViewModel()
{
_persons = new ObservableCollection<Person>();
_persons.Add(new Person() { Name = "Paul", Checked = false });
_persons.Add(new Person() { Name = "Brian", Checked = true });
}
private ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons
{
get { return _persons; }
}
}
public class Person
{
public String Name { get; set; }
public Boolean Checked { get; set; }
}
You should be able to see the message "Checked items=1".
Hope this helps
Since it was 2 way binding i could access the values selected by the checkboxes from the item source of listbox.
DataTable lstBoxMarketItemSourceDT = ((DataView)lstBoxMarket.ItemsSource).ToTable();
"Checked" column in the data table retrieved gives the updated check box values.