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.
Related
I am using the sample application provided along with the Gong solutions drag drop library.
The solution includes a listbox having itemssource and displaymemberpath set.
I have modified the application to include an itemscontrol and itemTemplate.
But the solution no longer works. There is an exception in the DragInfo.cs file.
Not sure if posting it here is correct.
But can someone help me with this. The sample code is pretty basic.
<ItemsControl
dragDropFramework:DragDrop.IsDragSource="True"
Grid.Column="0" ItemsSource="{Binding Pupils}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding FullName}" BorderBrush="Brown" BorderThickness="2" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Schools}"
dragDropFramework:DragDrop.DropHandler="{Binding}"
dragDropFramework:DragDrop.IsDropTarget="True"
Grid.Column="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Name}" BorderBrush="Brown" BorderThickness="2" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
class PupilViewModel
{
public string FullName { get; set; }
}
internal class WindowViewModel : IDropTarget
{
public ICollectionView Schools { get; private set; }
public ICollectionView Pupils { get; private set; }
public SideWindowViewModel()
{
var pupils = new ObservableCollection<PupilViewModel>
{
new PupilViewModel { FullName = "Alex Thompson" },
new PupilViewModel { FullName = "Tabitha Smith" },
new PupilViewModel { FullName = "Carl Pederson" },
new PupilViewModel { FullName = "Sarah Jones" },
new PupilViewModel { FullName = "Paul Lowcroft" }
};
this.Pupils = CollectionViewSource.GetDefaultView(pupils);
var schools = new SchoolViewModel { Name = "FirstSchool", Pupils = new ObservableCollection<PupilViewModel>() };
this.Schools = CollectionViewSource.GetDefaultView(schools);
}
public void DragOver(DropInfo dropInfo)
{
if (dropInfo.Data is PupilViewModel)// && dropInfo.TargetItem is SchoolViewModel)
{
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
dropInfo.Effects = DragDropEffects.Move;
}
}
public void Drop(DropInfo dropInfo)
{
throw new NotImplementedException();
}
The Window's dataContext, is set to an instance of WindowViewModel.
This code comes with Gong library also as a part of code project.
http://www.codeproject.com/Articles/43614/Drag-and-Drop-in-WPF
original code looks like this
<ListBox Grid.Column="1" ItemsSource="{Binding Schools.CurrentItem.Pupils}" DisplayMemberPath="FullName"
dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDropTarget="True"/>
In case you haven't figured this one out yet - it looks like you commented out where it checks to see if the thing you are dragging over is a SchoolView. Since you are using DropTargetAdorners.Highlight it is trying to highlight what you are dragging over. Since there isn't anything you're getting the null reference error. So maybe go back to this?
public void DragOver(DropInfo dropInfo)
{
if (dropInfo.Data is PupilViewModel) && dropInfo.TargetItem is SchoolViewModel)
{
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
dropInfo.Effects = DragDropEffects.Move;
}
}
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; }
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.
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>
I have an ItemsControl with a DataTemplate that has been defined. My ItemsControl definition looks like the following:
<ItemsControl x:Name="myItemsControl" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is a simplified version of my DataTemplate. Regardless, when a user clicks a button on the page, I want to loop through the items in myItemsControl and determine if the CheckBox element associated with the item is checked.
How do I determine if a CheckBox is checked for a specific item within an ItemsControl?
Thank you!
Add a property to your data class and databind it, then iterate over the collection itself.
public class myDataClass
{
public string Name { get; set;}
public bool IsSomething { get; set; }
}
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
You can try something like traditional iteration:
public bool? TestMyCheckbox(string bindingName)
{
foreach (var item in myItemsControl.Items)
{
if (item.GetType() == typeof(CheckBox))
{
var checkbox = (CheckBox)item;
if (checkbox.Content.Equals(bindingName))
{
return (checkbox.IsChecked);
}
}
}
return null;
}
Additionaly (this may better fit your needs) you can look for a list of checkboxes bindings that are checked:
public IEnumerable<object> TestMyCheckboxes(ItemsControl control)
{
return from Control x in control.Items
where x.GetType().Equals(typeof(CheckBox)) && ((CheckBox)x).IsChecked == true
select ((CheckBox)x).Content;
}