To clarify what is going on. Basically I have a ListView binding which points to a List within an object. Within that same object (but not within the list) I have another list which holds strings used for a dropdown and I cannot assign it to my list view as the DataContext is already set to the first list mentioned. Can someone please offer a solution, or better yet a more efficient way to handle this?
View
<ListView ItemsSource="{Binding myModel.myCollection}" Grid.Row="1" Grid.Column="0">
<ListView.View>
<GridView>
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name, Mode=TwoWay}"></TextBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Category Tag">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding myModel.CategoryList}"></ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Model
public class SiteUrlsModel : INotifyPropertyChanged
{
public string CaseName { get; set; }
public List<string> TestList => new List<string> { "Test1", "Test2", "Test3" };
public List<string> _categoryTagList;
public List<string> CategoryTagList
{
get => _categoryTagList;
set
{
if (_categoryTagList == value)
return;
_categoryTagList = value;
OnPropertyChanged();
}
}
private ObservableCollection<SiteUrlsModel> _myCollection;
public ObservableCollection<SiteUrlsModel> myCollection
{
get => _siteurlscCollection;
set
{
if (_siteurlscCollection == value)
return;
_siteurlscCollection = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
For simplicity I have excluded the ViewModel and Code-Behind but after InitialiseComponent() I have DataContext = new TestViewModel() and in my ViewModel I have a property which creates a new instance of my Model as well as adding a getter to ensure everything is accessible. Rest assured the list gets populated I am simply trying to populate one dropdown separately.
This is happening because, the Combo Box's datacontext will be myModel's item.
You need to explicitly tell the combo box to get the itemssource from it's parent's datacontext.
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.myModel.CategoryList, RelativeSource={RelativeSource AncestorType=DataGrid}}"></ComboBox>
</DataTemplate>
Related
I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
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"
xmlns:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"
I am using the following xaml code:
<ListView ItemsSource="{Binding Offsets, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="X" Width="90">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding X, Mode=TwoWay}"
Width="70"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
(I know this looks a little sloppy, but I am trying to keep it minimal)
The Offsets property I am binding to is a public List<Point3D> { get; set;}. (using System.Windows.Media.Media3D).
As such each Point3D has a public X,Y and Z property.
My ListView generates fine, but when I try to change a value of a TextBox, the Datacontext isn't updated.
What am I doing wrong?
If you are talking about Point3D Structure
Yes it has XYZ public properties but I don't think it implements INotifyPropertyChanged
Probably you forgot to implement the Interface INotifyPropertyChanged at your model or viewmodel classes.
You need to implement INotifyPropertyChanged interface in your class. Your binding mode is fine. You should be able to see changes.
public class YourClassName: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public ObservableCollection<YourModel> Offsets
{
get {return this.offsets;}
set
{
if (value != this.offsets)
{
this.offsets= value;
NotifyPropertyChanged("Offsets");
}
}
}
}
This seems so fundamental that I feel I must be missing something in my search for a solution.
I have a ViewModel that has a ObservableCollection< ExcelField> property which is used as a ItemsSource for a ListView in the associated View.
I have added a EventToCommand item in the View which accesses a RelayCommand to pass the Command to a method(ExcelListChanged) in the ViewModel with a parameter which is set to the SelectedItems property in the ViewList.
In my ViewModel would like to construct another List containing the items that have been selected. In the debugger I am able to examine the contents of the object parameter and validate that it indeed holds the information I would like to access. Unfortunately I have not been able to find the key that enables me to access the data in the parameter object, I have tried casting, assigning, ChangeTo and more. The cast and ChangeTo exception with a unknown conversion or something similar. I thought since the object is a ListView.SelectedItems(?) that I might be able to cast it to some flavor of that but it appears that is not doable outside of a ListView object.
A simple solution would be great, a complicated one would be okay and a convoluted one painful.
Thanks for any guidance.
Here are the code pieces
The data structure
public struct ExcelField
{
private int index;
public int Index
{
get { return index; }
private set { index = value; }
}
private string fieldName;
public string FieldName
{
get { return fieldName; }
private set { fieldName = value; }
}
public ExcelField(int ndx, string name)
{
index = ndx;
fieldName = name;
}
}
Pieces from the ViewModel
ObservableCollection<ExcelField> fieldNames;
public ObservableCollection<ExcelField> FieldNames
{
get
{
return fieldNames;
}
set
{
fieldNames = value;
OnPropertyChanged("FieldNames");
}
}
allListChanged = new RelayCommand(args => ExcelListChanged(args));
RelayCommand allListChanged;
public RelayCommand AllListChanged
{
get
{
return allListChanged;
}
}
private void ExcelListChanged(object parameter)
{
var whata = parameter.GetType();
return;
}
And finally the View pieces
<UserControl.Resources>
<ViewModel:ExcelMapperViewModel x:Key="ExcelMapperViewModelDataSource" d:IsDataSource="True"/>
<DataTemplate x:Key="ExcelFieldTemplate">
<StackPanel>
<TextBlock Text="{Binding FieldName, Mode=OneWay}"/>
<TextBlock Text="{Binding Index, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<ListView x:Name="AllFields" Grid.Column="1" Grid.Row="1" Grid.RowSpan="3"
ItemsSource="{Binding FieldNames}"
ItemTemplate="{StaticResource ExcelFieldTemplate}"
>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Index}" Header="Index"/>
<GridViewColumn DisplayMemberBinding="{Binding FieldName}" Header="Field Name"/>
</GridView>
</ListView.View>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<Custom:EventToCommand
Command="{Binding AllListChanged, Mode=OneWay}"
CommandParameter="{Binding ElementName=AllFields, Path=SelectedItems}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
I set a breakpoint on ExcelListChanged and run the code. When I select items in the ListView ExcelListChanged is triggered and I can examine the parameter object. I am able to see the number of entries selected as a Count on parameter and a array of ExcelField items with the appropriate information for the fields within the structure.
So how do I access the information I can see in the debugger programmatically?
since SelectedItems is an IList http://msdn.microsoft.com/en-us/library/system.collections.ilist(v=vs.110).aspx
you can simply iterate through it...
private void ExcelListChanged(object parameter)
{
IList iConverted= parameter as IList;
if(iConverted!=null){
foreach(YouKnowTheTypeOfElements theElement in iConverted) {
doSomethingWith(theElement);
}
}
return;
}
Have got myself very confused on this one - apologies if the answer is really obvious but I am quite new to programming.
I have a user control set up as a view which is loaded into a content control within my main view. The datacontext for the usercontrol (Called SetView) is set in the MainView. I can happily bind to the SetView to UserControlVM (called SetVM).
In the SetVM I load in an ObservableCollection of a class I have created:
public class WeightSet : Weights
{
public string BodyArea { get; set; }
public string ExerciseType { get; set; }
public int SetNumber { get; set; }
public static ObservableCollection<int> Reps { get; set; }
#region Constructor
//This is the main constructor
public WeightSet(string bodyarea, string exerciseType, int setNumber)
{
BodyArea = bodyarea;
ExerciseType = exerciseType;
SetNumber = setNumber;
Reps = new ObservableCollection<int>();
AddReps();
}
#endregion Constructor
#region Methods
public void AddReps()
{
for (int i = 1; i < 100; i++)
{
Reps.Add(i);
}
}
#endregion Methods
My SetView then has a ListView whose ItemsSource is
public ObservableCollection<WeightSet> Sets
Here is the xaml for the ListView:
<UserControl x:Class="CalendarTest.SetView"
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"
xmlns:forcombo="clr-namespace:CalendarTest.Model.Repository.Local_Data"
xmlns:VM="clr-namespace:CalendarTest.ViewModel"
mc:Ignorable="d"
d:DesignHeight="165" d:DesignWidth="300">
<Grid >
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding CurrentExercise}" Width="100" Height="40"></Label>
<Label Content="{Binding BodyArea}" Width="100" Height="40"></Label>
</StackPanel>
<ListView ItemsSource="{Binding Sets}">
<ListView.View>
<GridView>
<GridViewColumn Header="Set Number" DisplayMemberBinding="{Binding Path=SetNumber}" Width="100"></GridViewColumn>
<GridViewColumn Header="Select Reps" Width="120">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="100" ItemsSource="{Binding Source={x:Static forcombo:WeightSet.Reps }}" ></ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=Reps}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
When I load SetView I have my list of set numbers and a combobox with the staticlist. Here is a shot:
I cant seem to bind the selecteditem for the comboBox back to my ViewModel. Is there a way to set the selected item in the WeightSet Class? I need to be able to save the selected number?
I understand that I cant just bind to a property as the number of ComboBoxs will be decided by user? Any tips or corrections to my current design would be much appreciated
You can bind the SelectedItem property on your combobox, and add a dependencyproperty in your WeightSet class
xaml:
<ComboBox ItemsSource="{Binding Source={x:Static forcombo:WeightSet.Reps }}"
SelectedItem="{Binding SelectedItem}" />
and the property
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof (int), typeof (WeightSet), new PropertyMetadata(default(int)));
public int SelectedItem {
get { return (int) GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Then SelectedItem in your Sets instances will be updated when a value is selected in the combobox.
The key here is to bind the combobox's SelectedItem dependency property to a suitable property in the view model
First off, you need to Implement INotifyPropertyChanged in your Weightset class.
Next introduce a property in WeightClass called say SelectedRep which would look like
int _selectedRep;
///<summary>Gets or sets SelectedRep.</summary>
public int SelectedRep
{
get { return _selectedRep; }
set { _selectedRep = value; OnPropertyChanged("SelectedRep"); }
}
Finally Modify Xaml to bind the ComboBox's SelectedItem to the SelectedRep property.
<ComboBox Width="100" ItemsSource="{Binding Source={x:Static forcombo:WeightSet.Reps }}" SelectedItem="{Binding SelectedRep, Mode=TwoWay}" />
the XAML of my window:
<ListView Grid.Row="0" Name="files">
<ListView.Resources>
<DataTemplate x:Key="CheckboxTemplate">
<CheckBox IsChecked="{Binding Save, Mode=TwoWay}" />
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header=" " Width="30" CellTemplate="{StaticResource CheckboxTemplate}" />
<GridViewColumn Header="Datei" DisplayMemberBinding="{Binding File}"/>
</GridView>
</ListView.View>
</ListView>
the constructor of my Window:
IEnumerable<SaveItem> sil = sdl.Select(d => new SaveItem() { Save = true, Document = d });
files.ItemsSource = sil;
and the datastructure i want to display:
public class SaveItem : INotifyPropertyChanged
{
private bool save;
public bool Save
{
get { return this.save; }
set
{
if (value != this.save)
{
this.save = value;
NotifyPropertyChanged("Save");
}
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public StandardDocument Document { get; set; }
public string File { get { return Document.Editor.File; } }
#region INotifyPropertyChanged Member
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
i call the window. The window appears. I uncheck a checkbox of an item of the listview. i click a button. in its event-handler i read out the itemssource of the listview and ... the Save-Property of the Unchecked Item is (in its source) still true!
where is my mistake? why does my sources not get updated if i check/uncheck a checkbox?
You have not set your data context. If you are all in the same class - put something like this in your constructor of the window.
DataContext = this;
I think you need to set the DataContext to the code behind and then for clarity bind to the path.
XAML to set the Window DataContext
DataContext="{Binding RelativeSource={RelativeSource Self}}"
try converting IEnumerable to list..
it is not suggested to use IEnumerable as item source particularly when item source is evaluated using Linq
List<SaveItem> sil = sdl.Select(d => new SaveItem() { Save = true, Document = d }).ToList<SaveItem>();
files.ItemsSource = sil;