Binding List of objects to ListView not working - wpf

There is a problem with my ListView binding, it is not showing anything in the list.
I have the following ViewModel:
namespace Users.ViewModel
{
public class AllUsersViewModel
{
public List<Module> _modules = new List<Module>();
#region Constructor
public AllUsersViewModel()
{
this.SetModuleList();
}
void SetModuleList()
{
_modules = ModuleRepository.GetModules();
}
}
}
And the GetModules() function is return the set just fine. I have done a check by loop through the _modules List.
Here is my XAML:
<ListBox x:Name="lstModules" Width="190" ItemsSource="{Binding _modules}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ModuleName}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
What is the problem, why doesn't the ListBox populate?

_modules is a field, and you cannot bind to fields. Make a property which exposes the value of _modules:
public IEnumerable<Module> Modules
{
get { return _modules; }
}
and bind to this property instead:
<ListBox x:Name="lstModules" Width="190" ItemsSource="{Binding Modules}">

The Modules collection should be a property, not a variable.
public List<Module> Modules { get; set; }

Related

Get one element from an ItemsControl and binding to a Button

I have an ItemsControl:
<ItemsControl x:Name="myList" ItemTemplateSelector="{DynamicResource mySelectorTemplate}" ItemsPanel="{StaticResource myPanelTemplate}"/>
I want to take one of the items and make a button with his logic:
<Button Style="{StaticResource myButtonStyle}"/>
One of the items of myList has a boolean with true value. This is the item with which the button will be made:
Private myCollection As ObservableCollection(Of Items.Subitems)
myList.ItemsSource = myCollection
When myCollection.myBooolean = true, is the field that gives to the item this special treatment.
What would be an efficient way to do this?
There are many possibilitys for this - but this looks like the best way.
You want to use a DataTemplateSelector. You can choose n DataTemplates with this by your own overwritten logic.
C#
Sample Class:
public class MySampleClass
{
public string Name;
public bool MyFlag;
}
Sample Template Selector
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate IsTrueTemplate
{
get;
set;
}
public DataTemplate IsFalseTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MySampleClass temp = item as MySampleClass;
if(temp != null)
{
if (temp.MyFlag)
{
return IsTrueTemplate;
}
else
{
return IsFalseTemplate;
}
// And so on
}
else
{
return base.SelectTemplate(item, container);
}
}
}
Usage in XAML
<ListBox>
<ListBox.ItemTemplateSelector>
<example:MyTemplateSelector>
<example:MyTemplateSelector.IsTrueTemplate>
<DataTemplate>
<StackPanel>
<Button Style="{StaticResource myButtonStyle}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</example:MyTemplateSelector.IsTrueTemplate>
<example:MyTemplateSelector.IsFalseTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</example:MyTemplateSelector.IsFalseTemplate>
</example:MyTemplateSelector>
</ListBox.ItemTemplateSelector>
</ListBox>
example is your DataTemplateSelector-Namespace.
I would recommend you - to store the DataTemplates in the Resources and set it via StaticResource.

ListBox DataTemplate for dynamically loaded type

I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.

Binding a Combobox within a DataTemplate correctly in Wpf

I am trying to bind the ComboBox below to the list of Characters in the ObservableCollection, but it wont show anything. Any ideas why?
XAML:
<TabControl ItemsSource ="{Binding TextEditors}"
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox> ItemsSource="{Binding TextLines}"
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<ComboBox
ItemsSource="{Binding DataContext.InvCharacter, RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}"
DisplayMemberPath="name"
SelectedValuePath="cid"
SelectedValue="{Binding cid}">
</ComboBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
this is the class i am referring to:
class TextEditorVM: IViewModel {
public ObservableCollection<TextLineVM> TextLines { get { return textLines; } set { textLines = value;} }
public ObservableCollection<T_Character> InvCharacters { get { return invCharacters; } set { invCharacters = value; } }
public TextEditorVM(T_Dialogue dialogue)
{
DialogueManager.Instance.Register(this);
this.TextLines = new ObservableCollection<TextLineVM>();
this.InvCharacters = new ObservableCollection<T_Character>();
}
}
and the MainVM:
class MainVM : IViewModel
{
public ObservableCollection<TextEditorVM> TextEditors { get { return textEditors; } set { textEditors = value; OnPropertyChanged("TextEditors"); }
}
my T_Character Class looks like this now :
public class T_Character
{
public String cid { get; set; }
public String name { get; set; }
public T_Character(String cid, String name)
{
this.cid = cid;
this.name = name;
}
}
The DataContext of the TabControl is of type MainVM. The RelativeSource of the ComboBox binding should not be the TabControl but rather the ListBox.
Your InvCharacters property is on your TextEditorVM object which is in your ObservableCollection, however your binding is referencing TabControl.DataContext, which is MainVM, and does not contain that property.
Switch your RelativeSource binding to reference TabItem (it gets created automatically when you bind TabControl.ItemsSource) or ListBox to reference your TextEditorVM object
<ComboBox ItemsSource="{Binding DataContext.InvCharacters,
RelativeSource={RelativeSource AncestorType={x:Type TabItem}}}">
</ComboBox>

How do I bind IsChecked property of CheckBox within an ItemsControl?

I've got the following ItemsControl that gives me a check box for every database within the available collection. These checkboxes allow the user to select which ones to filter on. The databases to filter on are in a separate collection (FilteredDatabases). How exactly do I do this? I could add an InFilter property to the database item class. But, I don't want to start changing this code yet. The problem I can't get around in my head is the fact that I need to bind to a property that is not on the database item itself. Any ideas?
<ItemsControl ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding ???}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// In view model
public IBindingList FilteredDatabases
{
get;
private set;
}
public IBindingList AvailableDatabases
{
get;
private set;
}
Bind CheckBox.Command to routed command instance
Bind routed command to method
Use IBindingList.Add and IBindingList.Remove methods
The following code illustrates what you are trying to do, in order to do this you are better off using ObservableCollection instead of as your collection object, if an ItemsControl is bound to it it will automatically update the UI when viewmodels are added and removed.
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="0" ItemsSource="{Binding AvailableDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.Column="1" ItemsSource="{Binding FilteredDatabases}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
View Models:
public class MainViewModel
{
private ObservableCollection<DBViewModel> _availableDatabases;
private ObservableCollection<DBViewModel> _filteredDatabases;
public ObservableCollection<DBViewModel> AvailableDatabases
{
get
{
if (_availableDatabases == null)
{
_availableDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>()
{
new DBViewModel(this) { Name = "DB1" , IsChecked = true},
new DBViewModel(this) { Name = "DB2" },
new DBViewModel(this) { Name = "DB3" },
new DBViewModel(this) { Name = "DB4" },
new DBViewModel(this) { Name = "DB5" },
new DBViewModel(this) { Name = "DB6" },
new DBViewModel(this) { Name = "DB7" , IsChecked = true },
});
}
return this._availableDatabases;
}
}
public ObservableCollection<DBViewModel> FilteredDatabases
{
get
{
if (_filteredDatabases == null)
_filteredDatabases = new ObservableCollection<DBViewModel>(new List<DBViewModel>());
return this._filteredDatabases;
}
}
}
public class DBViewModel
{
private MainViewModel _parentVM;
private bool _isChecked;
public string Name { get; set; }
public DBViewModel(MainViewModel _parentVM)
{
this._parentVM = _parentVM;
}
public bool IsChecked
{
get
{
return this._isChecked;
}
set
{
//This is called when checkbox state is changed
this._isChecked = value;
//Add or remove from collection on parent VM, perform sorting here
if (this.IsChecked)
_parentVM.FilteredDatabases.Add(this);
else
_parentVM.FilteredDatabases.Remove(this);
}
}
}
View models should also implement INotifyPropertyChanged, I omitted it since it was not necessary in this particular case.

WPF databinding update comboxbox2 based on selection change in combobox1 with MVVM

I have a combo box that I have bound to a list that exists in my viewmodel. Now when a users makes a selection in that combo box I want a second combo box to update its content.
So, for example, combobox1 is States and combobox2 should contain only the Zipcodes of that state.
But in my case I don't have a predefined lists before hand for combobox2, I need to go fetch from a db.
Also, if needed, I could get all the potential values for combobox2 (for each combobox1 value) before hand, but I'd like to avoiding that if I can.
How do I implement in WPF and using MVVM? I'm fairly new to this whole wpf\databinding\mvvm world.
Something like the following. Note that the code is drastically simplified for the sake of example. In reality, your ViewModel would implement INotifyPropertyChanged and raise PropertyChanged events when the properties were modified.
The key though is the setter of SelectedState. Your ComboBox would bind its SelectedValue property to the ViewModel's SelectedState property. When the property changed, the ZipCodes collection gets re-loaded which another combobox would be bound to.
class MyViewModel {
public ObservableCollection<string> States {
get;
private set;
}
public ObservableCollection<string> ZipCodes {
get;
private set;
}
public string SelectedState {
get { return _selectedState; }
set {
_selectedState = value;
LoadZipCodes(_selectedState);
}
}
public string SelectedZipCode {
get;
set;
}
void LoadZipCodes(string state) {
// repopulate the ZipCodes property
}
}
Another solution. The approximate model:
class StateViewModel
{
public string StateName
{
get {...}
set {...}
}
public ObservableCollection<ZipCodeViewModel> ZipCodes
{
get {...}
set {...}
}
}
class ZipCodeViewModel
{
public string ZipCodeName
{
get {...}
set {...}
}
}
class MainViewModel
{
public ObservableCollection<StateViewModel> States
{
get {...}
set {...}
}
}
And XAML:
<ComboBox ItemsSource="{Binding Path=States}" IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=StateName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Content="{Binding Path=States}">
<ContentControl.ContentTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=ZipCodes}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=ZipCodeName}"></Label>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

Resources