DataBinding an ItemsSource inside an ItemsControl - c#

I still see my self as a beginner developer when it comes to wpf. I would like to know 2 things
where I went wrong or
how can I troubleshoot to find the solution.
Situation: [DATA part] I Have:
DataModel object. DataFilter object which is basically collection of DataModels + added functions. and DataFiltersGroup, which is used in DataViewModel and has collection of DataFilters I have a DataViewModel object which is basically an observable collection of items. I want to display each DataFilter in an Itemscontrol.
[Current solution] I have build a specialcombo control which derives from combobox [basically a button +combobox]. The specialcombo works fine when deliberately bound. So I am fairly confident the problem is not with special combo. When I set ItemsControl.ItemsSource property to the collection of DataFilters and make a DataTemplate of SpecialCombo, the combobox does not show any result (Special combo will not show toggle button if there are no Items - only button will show). An alternative - approach (2) to binding below let me see the dropdown togglebutton, but dropdown is empty however I know it shouldn't be.
here is sumarized extracts of code
public class DataModel :INotifyPropertyChanged
{
//The Item!!
public int Index {//Normal get set property}
public string Name {//Normal get set property}
public int Parent {//Normal get set property}
public string FullName {//Normal get set property}
public string DisplayName {//Normal get set property}
public bool Static {//Normal get set property}
}
public class DataFilters : DataCollection
{
public ObservableCollection<DataModel> CombinedData;
public int FilterIndex{//Property... The index of the current item like Index for DataModel.}
public string ParentName {//property ButtonContent item}
public int SelectedItem {//Property}
}
//Used as part of DataVieModel. Also responsible of building each DataFilters item and some other functions
public class DataFilterGroup : INotifyPropertyChanged
{
public ObservableCollection<DataFilters> FullCollection;
}
The WPF object
<ItemsControl x:Name="PART_ListBox" HorizontalAlignment="Left" VerticalContentAlignment="Stretch" Margin="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Code behind for WPF on load
//DVM = DataVieModel with some other objects. Filters = DataFilterGroup
PART_ListBox.ItemsSource = DVM.Filters.FullCollection;
PART_ListBox.ItemTemplate = DataFilterTemplate;
//And DataTemplate (1) - shows no combobox
private static DataTemplate DataFilterTemplate
{
get
{
DataTemplate DFT = new DataTemplate();
DFT.DataType = typeof(DataFilters);
FrameworkElementFactory Stack = new FrameworkElementFactory(typeof(VirtualizingStackPanel));
Stack.SetValue(VirtualizingStackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory Item = new FrameworkElementFactory(typeof(SpecialCombo));
Item.SetValue(SpecialCombo.ButtonContentProperty, new Binding("ParentName"));
Item.SetValue(SpecialCombo.ItemsSourceProperty, "CombinedData");
Item.SetValue(SpecialCombo.DisplayMemberPathProperty, "DisplayName");
Item.SetValue(SpecialCombo.SelectedValuePathProperty, "Index");
Item.SetValue(SpecialCombo.SelectedValueProperty, "SelectedItem");
//Item.SetValue(SpecialCombo.ToggleVisibleProperty, new Binding("ComboVisibility"));
//Item.SetValue(SpecialCombo.SelectedValueProperty, new Binding("SelectedItem"));
Stack.AppendChild(Item);
DFT.VisualTree = Stack;
return DFT;
}
}
//And DataTemplate (2) - shows combobox with no items in dropdown
private static DataTemplate DataFilterTemplate
{
get
{
DataTemplate DFT = new DataTemplate();
DFT.DataType = typeof(DataFilters);
FrameworkElementFactory Stack = new FrameworkElementFactory(typeof(VirtualizingStackPanel));
Stack.SetValue(VirtualizingStackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory Item = new FrameworkElementFactory(typeof(SpecialCombo));
Item.SetValue(SpecialCombo.ButtonContentProperty, new Binding("ParentName"));
Item.SetValue(SpecialCombo.ItemsSourceProperty, new Binding("CombinedData"));
Item.SetValue(SpecialCombo.DisplayMemberPathProperty, "DisplayName");
Item.SetValue(SpecialCombo.SelectedValuePathProperty, "Index");
Item.SetValue(SpecialCombo.SelectedValueProperty, "SelectedItem");
//Item.SetValue(SpecialCombo.ToggleVisibleProperty, new Binding("ComboVisibility"));
//Item.SetValue(SpecialCombo.SelectedValueProperty, new Binding("SelectedItem"));
Stack.AppendChild(Item);
DFT.VisualTree = Stack;
return DFT;
}
}

Thanks to punker 76 in another post where I restructured the question (here -> Can one bind a combobox Itemssource from a datatemplate of a ItemsControl) slightly the following had to be done.
DataFilters should become a dependencyobject therefore
public class DataFilters : DataCollection
// should become
public class DataFilters : DependencyObject
Observalbe collection should also change. so
public ObservableCollection<DataModel> CombinedData;
// should become
public static readonly DependencyProperty CombinedData= DependencyProperty.Register("CombinedData", typeof(ObservableCollection<DataModel>), typeof(DataFilters), new FrameworkPropertyMetadata());
//and should become a property
public ObservableCollection<DataModel> CombinedData
{
get { return (ObservableCollection<DataModel>)GetValue(CombinedDataProperty); }
set { SetValue(CombinedDataProperty, value); }
}
Then in the DataTemplate file the following should change
Item.SetValue(SpecialCombo.ItemsSourceProperty, "CombinedData");
//Should change to
Item.SetBinding(SpecialCombo.ItemsSourceProperty, new Binding("CombinedData") );
I haven't gone through the entire code above, but I think all the above changes should all be needed in order to bind a combobox type item in a DataTemplate.

Related

Items in a listbox updating erratically WPF

I have a simple list of strings which I want to be displayed in a listbox depending on if a checkbox is checked when a button is pressed. I have this logic in my button listener:
private void fileSavePerms_Click(object sender, RoutedEventArgs e)
{
foreach (CheckBox checkbox in checkboxList)
{
if (checkbox.IsChecked == true && !permissionList.Contains(checkbox.Name))
{
permissionList.Add(checkbox.Name);
}
else if (checkbox.IsChecked == false && permissionList.Contains(checkbox.Name))
{
permissionList.Remove(checkbox.Name);
}
}
permListBox.ItemsSource = permissionList;
}
As far as I know, this is how you can do a very simple data-bind on button click. However the listbox updates for the first time as intended, but then will update with incorrect contents of the list I am trying to populate the box with. I can see no discernible pattern with the output.
Furthermore, after a while (a few button clicks), I will catch an exception saying "an ItemsControl is inconsistent with its items source".
Am I setting up my binding incorrectly or assigning the ItemsControl at the incorrect time?
Update:
The XAML for the list box:
<ListBox x:Name="permListBox" ItemsSource="{Binding permissionList}" HorizontalAlignment="Left" Height="36" Margin="28,512,0,0" VerticalAlignment="Top" Width="442"/>
First of all you can bind only properties to a control. A field cannot be bound. So permissionList must be a property of the DataContext object you set to your Window.DataContext property.
If this is correctly set then you can create a new List<string> every time and then assign it to the property bound to the control. You do not have to assign it to the ItemsSource property of the control
Let's say your window's data context is set to the window itself.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public List<string> PermissionList
{
get { return (List<string>)GetValue(PermissionListProperty); }
set { SetValue(PermissionListProperty, value); }
}
public static readonly DependencyProperty PermissionListProperty =
DependencyProperty.Register(
"PermissionList",
typeof(List<string>),
typeof(MainWindow),
new PropertyMetadata(new List<string>())
);
private void fileSavePerms_Click(object sender, RoutedEventArgs e)
{
// You create a new instance of List<string>
var newPermissionList = new List<string>();
// your foreach statement that fills this newPermissionList
// ...
// At the end you simply replace the property value with this new list
PermissionList = newPermissionList;
}
}
In the XAML file you will have this:
<ListBox
ItemsSource="{Binding PermissionList}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="28,512,0,0"
Height="36"
Width="442"/>
Of course this solution can be improved.
You may use System.Collections.ObjectModel.ObservableCollection<string> type so that you no longer have to create a new instance of List<string> every time but you can clear the list and add the new items in your foreach statement.
You may use a ViewModel class (e.g. MainViewModel) that has this permission list and also implements the INotifyPropertyChanged interface and then you set an instance of this class to your WPF window's DataContext property.

WPF XAML Binding in another project

How can i bind a listbox for exemple to a method which is in another project like this :
Project1(BDD)
Class1
Project2(GUI)
class2
I want to bind a listbox itemSource in the second project(GUI), with a class created in my first project(BDD).
How can i bind a listbox for exemple to a method which is in another project?
Put simply, we don't do that in WPF. Instead, you should add a reference to the other project, add a using declaration and then instantiate an object from that class. Then (assuming that your method returns a collection of some sort) you should set the output of the method to a collection property that you then data bind to the ItemsSource property.
Here's a very basic example:
private ObservableCollection<int> numbers = new ObservableCollection<int>();
public ObservableCollection<int> Numbers
{
get { return numbers; }
set { numbers = value; NotifyPropertyChanged("Numbers"); }
}
...
Numbers = new YourClassFromOtherProject().GetData();
...
<ItemsControl ItemsSource="{Binding Numbers}" ... />

How to bind datatable value in combobox which has static value?

I have a ComboBox with few static values.
<ComboBox Name="cmbBoxField" Grid.Column="4" Grid.Row="2" Style="{StaticResource comboBoxStyleFixedWidth}" ItemsSource="{Binding}" ></ComboBox>
MVVMModle1.cmbBoxField.Items.Add(new CustomComboBoxItem("Text Box", "0"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Pick List", "1"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Check Box", "2"));
MVVMModle1.cmbBoxFieldType.Items.Add(new CustomComboBoxItem("Radio Button", "3"));
When I am saving the data in Database table it is getting saved.
((CustomComboBoxItem)(MVVMModle1.cmbBoxField.SelectedValue)).Value.ToString();
Now when I am trying to Edit my form and binding the value again to combobox it is not showing the value.
MVVMModle1.cmbBoxField.SelectedValue = dtDataList.Rows[0]["ControlList"].ToString().Trim();
Someone please help me in this. How to bind selected value to the combobox?
There are quite a few problems with your code here:
You are setting the ItemsControl.ItemsSource property to the default binding (bind to the current data context), which is incorrect unless the DataContext is any type that implements IEnumerable, which it probably isn't.
If this is correct because the DataContext is, for example, an ObservableCollection<T>, then you still have an issue because you are adding items statically to the ComboBox instead of whatever the ItemsSource is.
Also, the type of items you are adding are CustomComboBoxItem, which I'm going to assume inherits from ComboBoxItem. Either way, you can't say the SelectedValue is some string since the values in the ComboBox are not strings.
You should really not have a collection of CustomComboBoxItem's, but instead a custom class that is in itself it's own ViewModel.
Now that that's been said, here is a suggested solution to your problem:
<ComboBox ItemsSource="{Binding Path=MyCollection}"
SelectedValue="{Binding Path=MySelectedString}"
SelectedValuePath="StringProp" />
public class CustomComboBoxItem : ComboBoxItem
{
// Not sure what the property name is...
public string StringProp { get; set; }
...
}
// I'm assuming you don't have a separate ViewModel class and you're using
// the actual window/page as your ViewModel (which you shouldn't do...)
public class MyWPFWindow : Window, INotifyPropertyChanged
{
public MyWPFWindow()
{
MyCollection = new ObservableCollection<CustomComboBoxItem>();
// Add values somewhere in code, doesn't have to be here...
MyCollection.Add(new CustomComboBoxItem("Text Box", "0"));
etc ...
InitializeComponent();
}
public ObservableCollection<CustomComboBoxItem> MyCollection
{
get;
private set;
}
private string _mySelectedString;
public string MySelectedString
{
get { return _mySelectedString; }
set
{
if (String.Equals(value, _mySelectedString)) return;
_mySelectedString = value;
RaisePropertyChanged("MySelectedString");
}
}
public void GetStringFromDb()
{
// ...
MySelectedString = dtDataList.Rows[0]["ControlList"].ToString().Trim();
}
}
You could alternatively not implement INotifyPropertyChanged and use a DependencyProperty for your MySelectedString property, but using INPC is the preferred way. Anyways, that should give you enough information to know which direction to head in...
TL;DR;
Take advantage of binding to an ObservableCollection<T> (create a property for this).
Add your items (CustomComboBoxItems) to the ObservableCollection<T>.
Bind the ItemsSource to the new collection property you created.
Bind the SelectedValue to some string property you create (take advantage of INPC).
Set the SelectedValuePath to the path of the string property name of your CustomComboBoxItem.
Can you use cmbBoxField.DataBoundItem()? If not target the source from the selected value, i.e. Get the ID then query the source again to get the data.
(CustomComboBoxItem)MVVMModle1.cmbBoxField.DataBoundItem();
When you bind a datasource it is simpler to do it like this:
private List GetItems(){
List<CustomComboBoxItem> items = new List<CustomComboBoxItem>();
items.Add(new CustomComboBoxItem() {Prop1 = "Text Box", Prop2 = "0"});
//...and so on
return items;
}
Then in your main code:
List<CustomComboBoxItem> items = this.GetItems();
MVVMModle1.cmbBoxField.DisplayMember = Prop1;
MVVMModle1.cmbBoxField.ValueMember = Prop2;
MVVMModle1.cmbBoxField.DataSource = items;
This will then allow your selected value to work, either select by index, value or text
var selected = dtDataList.Rows[0]["ControlList"].ToString().Trim();
MVVMModle1.cmbBoxField.SelectedValue = selected;

Binding a datagrid to the selected item of another datagrid

Ive got two DataGrids. EmployeeGrid and WorkSessionsGrid. Each Employee has a list of WorkSessions that I want the user to access by selecting an Item in the EmployeeGrid which should make the WorkSessionsGrid generate the WorkSessions for the selected Employee. Why is the following not correct?
<DataGrid Name="dg_2" ItemsSource="{Binding ElementName=dg_1, Path=SelectedItem.WorkSessions}"/>
Update
I've come to the conclusion that the problem has to be in one of the other layers.
Here's the remainder of my code, hopefully someone is capable of helping me out.
Is there something fundamentally that I am missing?
Code-Behind xaml
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employee.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
sp_worksessions.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
menu_worksession.DataContext = EmployeeViewModel.SelectedEmployee.WorkSessions;
datagrid_worksessions.ItemsSource = EmployeeViewModel.SelectedEmployee.WorkSessions;
}
}
WorkSessionViewModel
class WorkSessionViewModel : ViewModelBase
{
private WorkSessions _workSessionsModel = new WorkSessions();
public WorkSessions WorkSessionsView = new WorkSessions();
private WorkSessionModel _selectedWorkSession = new WorkSessionModel();
public WorkSessionModel SelectedWorkSession
...
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
Try Binding to the Element in stead.
<DataGrid Content="{Binding ElementName=ListOfEmp, Path=SelectedItem.Name}" DataContext="{Binding}" />
This bit of XAML looks quite correct, try to debug the binding, there might be some other problems like visual tree breaks or the WorkSessions collection perchance is a field and not a property etc.
If there are binding errors please share them.
As #H.B. has pointed out correctly, please use your Visual Studio's Output Window to see any binding errors. They will tell you if the bindings are failing. If you find binding erros then yours binding should be solved for two possible issues...
Source of data is incorrect. Is the data context and items source correctly set for that UI element such as DataGrid?
Path of the property in the binding could be incorrect. Is your SelectedItem having an object has any property named WorkSessions in it? etc.
Apart from this we still dont know what dg_1 and dg_2 from your XAML is. Your code behind shows different names datagrid_employees and datagrid_worksessions I guess.
You should add one more item for the EmployeeViewModel, called: SelectedEmployee and bind that with the employee grid selected item, mode=TwoWay.
Then you databind for the second grid should be:
<DataGrid Name="dg_2" ItemsSource="{Binding Path=SelectedEmployee.WorkSessions}"/>
Since both grids are in the same window, thus, you should only set datacontext for the windows only. In side the viewmodel, you have 2 dependency properties: EmployeeList, SelectedEmployee. Whereas, EmployeeList is binded to ItemsSource of the employee grid. SelectedEmployee is binded to SelectedItem on the employee grid.

DataBinding Textbox.Text to a specific element of a string array

Delving into the wonderful world of .NET databinding. I have a textbox whose text property I want to bind to a specific element of a string array in another object. (The form contains a combobox that selects the index of the element).
In other words, I'd like to do something like this:
textBoxFictionShort.DataBindings.Add(
new Binding("Text", m_Scenario, "Fiction[int32.Parse(comboBoxSelector.Text)]"));
where m_Scenario contains the
public string[] Fiction { get; set; }
property that I source from. Obviously the Binding above won't retrieve my item. AFAIK I can't create properties that accept parameters. What's the elegant/correct solution for this when using databinding? I can think of several ugly-seeming workarounds (i.e. a string property in m_Scenario that references the array string I'm binding to, and a public function that updates the string property on the combobox SelectedIndexChanged event).
This is an excellent place to have a View Model. Here's another ViewModel link
What I would do is have the following in the ViewModel (that gets bound to by components on the view)
an IObservable property bound to the items source of the combo box, that you add/remove to depending on the size of the array
a int property for the selected index bound to the SelectedElement of the combo box. You would have to do the conversion from string to int when this property is being set.
a string property that is bound to the textbox.text (you could likely use a label here, by the by) that is updated each time the aforementioned int property for the selected index is changed.
If this at all confusing I can build up some pseudo code, but those three properties should work and get you what you want.
Edit - To add some code:
public class YourViewModel : DependencyObject {
public string[] FictionArray {get; private set;}
public IObservable<string> AvailableIndices;
public static readonly DependencyProperty SelectedIndexProperty=
DependencyProperty.Register("SelectedIndex", typeof(string), typeof(YourViewModel), new PropertyMetadata((s,e) => {
var viewModel = (YourViewModel) s;
var index = Convert.ToInt32(e.NewValue);
if (index >= 0 && index < viewModel.FictionArray.Length)
viewModel.TextBoxText=FictionArray[index];
}));
public bool SelectedIndex {
get { return (bool)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty TextBoxTextProperty=
DependencyProperty.Register("TextBoxText", typeof(string), typeof(YourViewModel));
public bool TextBoxText {
get { return (bool)GetValue(TextBoxTextProperty); }
set { SetValue(TextBoxTextProperty, value); }
}
public YourViewModel(string[] fictionArray) {
FictionArray = fictionArray;
for (int i = 0; i < FictionArray.Length; i++){
AvailableIndices.Add(i.ToString()));
}
}
}
This isn't perfect, but it should give you some idea how you could create a viewmodel with properties that you could bind to. So in your xaml you'd have something like:
<ComboBox ItemSource="{Binding AvailableIndices}" SelectedItem="{Binding SelectedIndex}"/>
<TextBox Text="{Binding TextBoxText}"/>
I think you are in WinForms (not WPF), in this case you could just bind directly to the ComboBox's SelectedValue property...
comboBox1.DataSource = m_Scenario.Fiction;
textBoxFictionShort.DataBindings.Add(new Binding("Text", comboBox1, "SelectedValue"));
Add BindingSource
...
bindingSource1.DataSource = m_Scenario.Fiction
.Select((x, i) => new {Key = i + 1, Value = x})
.ToDictionary(x => x.Key, x => x.Value);
comboBox1.DisplayMember = "Key";
comboBox1.DataSource = bindingSource1;
textBox1.DataBindings.Add("Text", bindingSource1, "Value");
}
}

Categories