Bindings in ItemsControl aren't working - wpf

I have a bunch of different controls (mainly buttons with textblocks inside them) that I just put in an ItemsControl. All the bindings worked correctly before I put the controls in the ItemsControl. Now, none of the commands work, or the Text bindings. Everything just shows up as 0's (I'm binding to doubles). I double-checked to make sure my ObservableCollection was actually filled with items, and that those items' properties actually had data in them. The ItemsControl does properly create a new row for each item in the collection.
Here's my model:
public class VehicleModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<double> _nowTime = new List<double>();
public List<double> NowTime
{
get { return _nowTime; }
set { _nowTime = value; OnPropertyChanged("Nowtime"); }
}
private List<double> _VehLat = new List<double>();
public List<double> VehLat
{
get { return _VehLat; }
set { _VehLat = value; OnPropertyChanged("VehLat"); }
}
private int _currentIteration;
public int CurrentIteration //used to hold current index of the list of data fields
{
get { return _currentIteration; }
set
{
_currentIteration = value;
OnPropertyChanged("CurrentIteration");
OnPropertyChanged("CurrentVehLat");
}
}
private double _currentVehLat;
public double CurrentVehLat
{
get { return _currentVehLat; }
set { _currentVehLat = VehLat[CurrentIteration]; OnPropertyChanged("CurrentVehLat"); }
}
}
//Used to loop through the above list and set the currentVehLat equal to
//the current iteration of the list
public void SetData(int i)
{
CurrentIteration = i;
}
In my viewmodel, I have an ObservableCollection holding these VehicleModels:
private ObservableCollection<VehicleModel> _vehicleCollection = new ObservableCollection<VehicleModel>();
public ObservableCollection<VehicleModel> VehicleCollection
{
get
{
return _vehicleCollection;
}
set
{
if (null != value)
{
_vehicleCollection = value;
OnPropertyChanged("VehicleCollection");
}
}
}
private ICommand showTimeWindowCmd;
public ICommand ShowTimeWindowCmd
{
get
{
return showTimeWindowCmd;
}
set
{
showTimeWindowCmd = value;
}
}
public MainWindowViewModel()
{
ShowTimeWindowCmd = new RelayCommand(ShowTimeWindow, param => this.canExecute);
}
public void ShowTimeWindow(object parameter)
{
//do stuff
}
Finally, the .xaml for my ItemsControl. I'm only showing one because there's a lot of them, but the are all exactly the same, just bound to different properties (all doubles like the one showed in the view model). Note: The controls show up properly, just not the bindings:
<ItemsControl Grid.Row="8"
Grid.ColumnSpan="16"
ItemsSource="{Binding VehicleCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
//bunch here
</Grid.ColumnDefinitions>
<Button Grid.ColumnSpan="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding ShowTimeWindowCmd}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="NowTime" />
<Binding Path="VehLat" />
<Binding Source="FISH Latitude" />
<Binding />
</MultiBinding>
</Button.CommandParameter>
<Button.Template>
<ControlTemplate>
<TextBlock FontSize="17"
Text="{Binding Path=CurrentVehLat,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
StringFormat={}{0:F7}}"
Visibility="{Binding IsChecked,
ElementName=FishChkBox,
Converter={StaticResource BoolToVisConverter}}" />
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit: I am setting my datacontext:
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
Edit 2: Added the command being called in the viewmodel. The first two command parameters are properties of the model.

As the DataContext for the ItemsSource is the collection of Model, for the inners controls this will be its DataContext too, so you need to explicitly specify the Path to point to ViewModel properties:
<Button Grid.ColumnSpan="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.ShowTimeWindowCmd}"
>

The DataContext for each <ItemTemplate> in your ItemsControl is set to the individual item.
So what is being rendered is
<ItemsControl ItemsSource="{Binding VehicleCollection}">
<ContentPresenter> <!-- DataContext is VehicleModel[0] -->
<Grid...>
<!-- DataContext is inherited, so still VehicleModel[0] -->
<Button Command="{Binding ShowTimeWindowCmd}" .. />
...
</Grid>
</ContentPresenter>
<ContentPresenter> <!-- DataContext is VehicleModel[1] -->
<Grid...>
<!-- DataContext is inherited, so still VehicleModel[1] -->
<Button Command="{Binding ShowTimeWindowCmd}" .. />
...
</Grid>
</ContentPresenter>
etc...
</ItemsControl>
You need to change the Source of the command bindings so that instead of pointing to the default DataContext.ShowTimeWindowCmd which results in VehicleModel.ShowTimeWindowCmd, they point to ItemsControl.DataContext.ShowTimeWindowCmd which looks from your code like it should result in MainWindowViewModel.ShowTimeWindowCmd
There's many ways to do that, but easiest to understand is by using the ElementName property of the binding.
<ItemsControl x:Name="MyItemsControl"...>
...
<Button Command="{Binding ElementName=MyItemsControl, Path=DataContext.ShowTimeWindowCmd}" .. />
...
</ItemsControl>
A RelativeSource binding would also work here if you don't want to hardcode a Name like this :
<ItemsControl ...>
...
<Button Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.ShowTimeWindowCmd}" .. />
...
</ItemsControl>

Related

CurrentViewModel Property Change Not Reflected in ContentControl

Hello I have looked everywhere and I can't understand what I am doing wrong.
My command is called and the onpropertychanged event is called but the ContentControl in my MainWindow will not show the next view.
I am new to WPF and Xaml and really trying to figure this out.
I have attached a link to the sample application on my Github.
The End game here is I want the Radio button to be checked when the view is active. I also want to be able to change from the home view to other views via a button on that view once I am past this first hurtle.
Github repository
public class ApplicationViewModel : ObservableObject
{
private ICommand _changeViewCommand;
private IViewModel _CurrentViewModel;
private List<IViewModel> _viewModels;
private bool _isActive;
public ApplicationViewModel()
{
ViewModels.Add(new HomeViewModel());
ViewModels.Add(new AccountViewModel());
CurrentViewModel = ViewModels.Find(r => r.Name == "Home");
CurrentViewModel.IsActive = true;
}
public List<IViewModel> ViewModels
{
get
{
if (_viewModels == null)
_viewModels = new List<IViewModel>();
return _viewModels;
}
}
public IViewModel CurrentViewModel
{
get
{
return _CurrentViewModel;
}
set
{
if (_CurrentViewModel != value)
{
_CurrentViewModel = value;
OnPropertyChanged();
}
}
}
public ICommand ChangePageCommand
{
get
{
if (_changeViewCommand == null)
{
_changeViewCommand = new RelayCommand(
p => ChangeViewModel((IViewModel)p),
p => p is IViewModel);
}
return _changeViewCommand;
}
}
private void ChangeViewModel(IViewModel viewModel)
{
CurrentViewModel = ViewModels.Find(r => r.Name == viewModel.Name);
}
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
OnPropertyChanged("IsActive");
}
}
}
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:ApplicationViewModel}"/>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<RadioButton Content="Home" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:HomeViewModel/>
</RadioButton.DataContext>
</RadioButton>
<RadioButton Content="Account" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:AccountViewModel/>
</RadioButton.DataContext>
</RadioButton>
</StackPanel>
you should connect RadioButtons to the ViewModels which you have in the list. don't create new view models in xaml.
and CurrentViewModel can be displayed via ContentControl with one of templates decalred in Resources
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}"
IsChecked="{Binding IsActive}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }">
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
after that you should probably change StackPanel to Grid to allow ContentControl to take all available screen space
Looks like if I assign the AccountViewModel to the button DataContext and set the RelativeSource AncestorType to x:Type Window the button on the HomeView will switch the view to the AccountView.
<Button Height="40" Content="Account" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }">
<Button.DataContext>
<viewModels:AccountViewModel/>
</Button.DataContext>
</Button>

How to put an Expander inside Datatemplate?

Strange one.
I have a contentcontrol on a WPF form, this loads a datatemplate within it.
This shows up fine (handwritten summary code so ignore errors/lack of attributes):
<DataTemplate>
<Label Content="Found datatemplate" />
</DataTemplate>
This however renders blank
<DataTemplate>
<Expander Header="Why dont I show">
<Label Content="Found datatemplate" />
</Expander>
</DataTemplate>
I have set the expander to visibile, isexpanded to true etc and no matter what it doesn't render at all.
Confused- is this just not possible?
I've recently done something similar to what you're describing and it worked for me. I have an ItemsControl that binds to a collection of view models, each of which contains a UserControl representing custom content. I implemented the ItemsControl.ItemTemplate to display the custom control inside an Expander like this:
<ItemsControl Margin="0,20,0,0" ItemsSource="{Binding ControlItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,0,0,0"
BorderBrush="#E7E7E7"
BorderThickness="0,1,0,0"
Padding="20,0">
<Expander Foreground="#E7E7E7"
IsExpanded="{Binding Path=IsExpanded,
Mode=TwoWay}">
<Expander.Header>
<Grid>
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="24"
Text="{Binding Title}" />
</Grid>
</Expander.Header>
<DockPanel>
<ScrollViewer MinHeight="250">
<ContentControl Content="{Binding Control}" />
</ScrollViewer>
</DockPanel>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is what my view model looks like:
public class SidePanelControlItem : ModelBase
{
private bool _isExpanded;
public SidePanelControlItem(UserControl control)
{
if (control == null) { throw new ArgumentNullException("control");}
Control = control;
}
public string Title { get; set; }
public UserControl Control { get; private set; }
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}

Checkbox not showing as checked inside ListView

I have a checkbox inside a listview. The listview is bound to an observable collection. When I use the context menu to try to select all Checkboxes, they do not show as checked. What am I doing wrong?
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AvailableModels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SelectionMode="Extended">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Select All Models" Command="{Binding
SelectAllModelsAction}" />
<MenuItem Header="Deselect All Models" Command="{Binding
DeselectAllModelsAction}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding DataContext.IsSelected,
RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type
ListViewItem}}}" VerticalAlignment="Center" />
<Label Content="{Binding Name}" Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<ListItems> AvailableModels
{
get
{
return this.availableModels;
}
set
{
this.availableModels = value;
this.NotifyPropertyChanged(m => m.AvailableModels);
}
}
Context Menu Action
private void SelectAllModels()
{
foreach (var model in this.AvailableModels)
{
model.IsSelected = true;
}
this.NotifyPropertyChanged(m => m.AvailableModels);
}
ListItems Object
public class ListItems
{
public string Name
{
get;
set;
}
public object Value
{
get;
set;
}
public bool IsSelected
{
get;
set;
}
}
ListItems isn't implementing INotifyPropertyChanged so changing IsSelected isn't updating the UI.
Calling NotifyPropertyChanged in SelectAllModels() is unnecessary since the collection itself isn't changed. The NotifyProperyChanged() call in the AvailableModels setter updates the UI when a new collection is set. And ObservableCollection will handle when the collection is modified (items added/removed). However changes to the properties within the ListItems does not update the UI unless they call NotifyPropertyChanged in the setters.

Adding UserControl dynamically on User interface

In my WPF application, I need to post n number of my user control in in form of rows and columns. My user control is like
The number of rows can be n, while number of columns will be either 1 or 2 depending on the view user chooses to use.
Here is the collection that contains my UserControls
private Collection<TemplateView> _templates;
public Collection<TemplateView> Templates { get { return _templates; } set { _templates = value; } }
And here is the XAML code that I used.
<ItemsControl ItemsSource="{Binding Templates}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding NumColumns}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<v:TemplateView Content="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
v:TemplateView is the UserControl whose n copied needs to be posted in rows/columns.
Here is some of the XAML showing binding of UserControl's controls.
<Label Content="{Binding Title, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Label Content="{Binding Type, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<TextBlock><Hyperlink Command="{Binding DetailsViewCommand}">Details</Hyperlink>
</TextBlock>
<TextBlock><Hyperlink Command="{Binding AddCommand}">Add</Hyperlink>
And here is my UserControl's VIewModel's code
private ICommand _detailsViewCommand;
public ICommand DetailsViewCommand { get { return _detailsViewCommand ?? (_detailsViewCommand = new RelayCommand(DetailsView)); } }
public void DetailsView()
{
}
private ICommand _addCommand;
public ICommand AddCommand { get { return _addCommand ?? (_addCommand = new RelayCommand(Add)); } }
private void Add()
{
}
private string _layerType;
public string LayerType
{
get { return _layerType; }
set { _layerType = value; }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; }
}
All copies of this UserControl will carry different information in labels and Image. So I need to know in the userControl's ViewModel, which UserControls (or which item in Templates Collection) has been clicked when user presses the Details button.
Above XAML and code does not tell me which item/user control was clicked on Details button click. So how should I accomplish the two tasks?

how to access the label inside datatemplate

hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.

Resources