WPF binding to CurrentItem - wpf

I have a WPF model which contains a viewsource, a listview, and a button, which is meant to execute an ICommand in the viewmodel for the item selected in the listview.
<Window.Resources>
<CollectionViewSource x:Key="teachingSessionsViewSource" d:DesignSource="{d:DesignInstance {x:Type local:TeachingSessionListItem}, CreateList=True}"/>
</Window.Resources>
...
<GroupBox Header="All Sessions"
DataContext="{Binding Source={StaticResource teachingSessionsViewSource}}">
<StackPanel>
<ListView x:Name="TeachingSessionList"
ItemsSource="{Binding}"
SelectedItem="{Binding Path=/}">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource noHeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding SessionDate}"/>
<GridViewColumn DisplayMemberBinding="{Binding PresenterInitials}" Width="30" />
</GridView>
</ListView.View>
</ListView>
<Button Content="Un-Assign" Margin="5,5" Command="{Binding Path=/UnAssignPresentation}"
</StackPanel>
</GroupBox>
The problem is that the ICommand UnAssignPresentation is always executed on the first item in the list, regardless of which item is selected in the listview. That is to say, the currentItem property of the CollectionViewSource does not seem to be bound to the ListView.
What am I doing wrong? Thank you very much.

Try adding IsSynchronizedWithCurrentItem="True" to your ListView.
Also, prefer binding listview to a collection in your view model and not to a resource.
You can then add a property to the view model for the selected item
and bind the listview's selected item to that property in the view model
and either bind the button's command to a command in the view model that acts on the current item,or pass the item you want to act on via the command parameter binded to the selected item.

Related

Which components of a ListView have to be explicitly provided when utilising Control Templates?

The solution to my question might just be specifically related to WPF ListViews, but it could also just be a control template issue in general. I don't know.
I have the following 3 property class, with overridden ToString method defined in my codebehind:
public class WorkListViewItem
{
public int Id { get; set; }
public string Namey { get; set; }
public bool d { get; set; }
public override string ToString()
{
return "ID: " + Id + "\r\nName: " + Namey + "\r\nd?:" + d.ToString();
}
}
I have a working ListView defined in my markup, as well as Data Templates defined under the window's resources for all 3 class properties, and an array of WorkListViewItem defined under an all-parent grid's resources.
(DTNamey also has two-way binding, a textbox, and a TextChanged event attached, but that makes no difference to my issue)
Data Templates:
<DataTemplate x:Key="DTNamey" DataType="{x:Type local:WorkListViewItem}">
<TextBox Text="{Binding Path=Namey, Mode=TwoWay}" Width="65" Foreground="BlueViolet" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="DTId" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=Id}" Foreground="GreenYellow"/>
</DataTemplate>
<DataTemplate x:Key="DTd" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=d}" Foreground="BlueViolet"/>
</DataTemplate>
Data Array:
<x:Array Type="{x:Type local:WorkListViewItem}" x:Key="wvlis">
<local:WorkListViewItem Id="1" Namey="Fred" d="True"/>
<local:WorkListViewItem Id="2" Namey="Beef" d="True"/>
<local:WorkListViewItem Id="3" Namey="Pork" d="False"/>
</x:Array>
ListView:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}">
<!--This actually works-->
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
Now this all does exactly what I want it to:
(I lack the reputation to post images, but trust me, it really does!)
It displays the 3 columns and a row for each element in the data array.
Changes to the textboxes for Namey are reflected in the second column (Length), and all is well in the world.
The Issue:
However, that same ListView definition, when placed in a Control Template, displays no items. The columns display just fine, but no rows are shown.
Window Resources:
<!--This does not work-->
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
</ControlTemplate>
Inside Grid:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}" Template="{StaticResource cmonnow}">
</ListView>
My Efforts:
I have tried adding ItemsPresenter controls in the columns, which clashes with the column header definitions.
I've tried adding the ItemsPresenter under:
<ListView.Items>
<ItemsPresenter/>
</ListView.Items>
I've tried setting the ListView's data context to: (totally incorrect, I'm sure)
<ListView DataContext="{Binding local:WorkListViewItem}">
I've even tried defining the ListView's ItemTemplate property.
I have been working at figuring this out for 3 days, and decided to turn to the internet for help.
So my question would be; which ListView properties are required to display the items collection when using Control Templates?
Thank you for your time.
Matthew
Update 29/09/2015
I've found that placing ItemsPresenter and ContentPresenter elements directly under the ListView tag, adds blank items that are correctly styled via the data templates.
Breakthrough!
I have found that by binding the template's ListView's ItemsSource property, to the ItemsSource provided, all the items are displayed, and all columns are correctly populated.
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView ItemsSource="{TemplateBinding ItemsSource}">
...
</ControlTemplate>
Figured it out
Boiling this down, it seems that the only component that is required to get a ListView control template to display data is the ItemsSource property of the template ListView.
The ListView View property does not need to be manually set in order to see items.
However, for custom objects such as mine, it may be advisable to override the ToString method, as this is what you will see without any further template definitions.
(On a side note, it was a little counter-intuitive to have to define a ListView inside a Control Template with its TargetType property set to "ListView", but you could count this as a requirement too)

WPF: Deselect all my Checkbox when one is selected

This is my first ListView row column that contain CheckBox:
<GridViewColumn Width="40" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="cbSelectInterface" IsThreeState="False" ></CheckBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
How can i add the option to check only one CheckBox in specific Row and what i choose another CheckBox and check this control all other CheckBox become non selected ?
What about binding the CheckBox's IsChecked property to the ListViewItem's IsSelected property? If SelectionMode is set to Single, then you'll only be able to select one row and one checkbox.
<ListView ItemsSource="{Binding Source={StaticResource TestDataSource}}"
SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Width="30">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource
AncestorType={x:Type ListViewItem}}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
You can then use the ListView's SelectedItem to know which row is the one with the checked CheckBox.
In WPF, there is no control that automatically does what you want. However, implementing your requirement is simple, or at least, would be simple if you were using data binding as is customary in WPF applications. In that case, your view model or code behind would have an object (that would be data bound to the ListView.ItemsSource property) that contains all of the data items.
All you need to do when one of the Checkboxes is checked is to iterate through each item in the data bound collection and set their properties that are data bound to the Checkbox to false:
foreach (var item in YourDataBoundCollection)
{
item.PropertyDataBoundToCheckBox = false;
}
If you have implemented the INotifyPropertyChanged interface on your data class as you should have, then the UI will be automatically updated.
The only remaining question is how do you know when a CheckBox has been checked? The simplest 'non MVVM' way would be to add a handler for the CheckBox.Checked Event.

ListView SelectionChanged won't update when enclosed TextBox clicked

I have a list view with a gridview. The gridview second column contains textboxes (and in case this is relevant to this particular problem, the textboxes are rendered by a datatemplate).
I want the listview SelectedItem to update when the user clicks in the textbox to type and reflect the row (or listview item) containing the textbox. Right now listview SelectedItem will update only if the user's click on a row is just outside the textbox. If I click the textbox or type in it, the highlighted row stays the same and no SelectionChanged occurs for the listview.
How can I get the listview's selecteditem to update? Can I maybe force a change in its selecteditem or enable the listview to see the textbox click as its own somehow?
Thanks in advance.
P.s. Solution located here:
https://drive.google.com/file/d/0B89vOvsI7UbdTmN5RVdLVTFGeXM/view?usp=sharing
ListView xaml:
<ListView x:Name="CombinedlistView" Grid.Column="0"
DataContext="{Binding ElementName=mainWindow}"
SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView >
<GridViewColumn CellTemplate="{StaticResource labelTemplate}" />
<GridViewColumn CellTemplateSelector="{StaticResource fieldValueTemplateSelector}" />
</GridView>
</ListView.View>
</ListView>
TemplateSelector:
<loc:FieldValueTemplateSelector x:Key="fieldValueTemplateSelector"
StringTemplate="{StaticResource stringTemplate}"
DateTimeTemplate="{StaticResource dateTimeTemplate}"/>
DataTemplate where textbox is located:
<DataTemplate x:Key="stringTemplate" DataType="{x:Type System:String}">
<StackPanel Style="{StaticResource dataTemplateStackPanelStyle}" Name="stringTemplStack">
<TextBox Name="searchValText" Width="120"
Text="{Binding Path=TextVal, Mode=OneWayToSource}"
DataContext="{Binding ElementName=mainWindow}"/>
</StackPanel>
</DataTemplate>
try wiring up this event to your PreviewGotKeyboardFocus event on your listviewitem:
protected void SelectCurrentItem(object sender, KeyboardFocusChangedEventArgs e)
{
ListViewItem item = (ListViewItem)sender;
item.IsSelected = true;
}

Data binding to a ViewModel property from a collections Binding?

I will try to keep this as succinct as possible.
I have a ViewModel with a collection of Models (i.e. Airplanes).
I have a Xaml page that is binded to the ViewModel.
The Xaml page has a DataGrid that is binded to the Airplanes collections.
For one of the DataGrid column templates, I want it to show a list of "EngineComponents", where EngineComponents is a collection of items defined in the ViewModel.
The catch is this:
The EngineComponents is a collection of parts that is essentially static. All Airplance rows in the DataGrid should show the same list of EngineComponents.
Airplanes
How to solve this EngineComponents binding question without writing extra code (event handlers, etc)?
You need to use a RelativeSource. I'll use a ListView in my example, but the ideas the same.
<UserControl ... DataContext="{Binding ...}">
<ListView ItemsSource="{Binding Airplanes}">
<ListView.View>
<GridView>
<GridViewColumn Header="Drawing No." Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AvailableAirplanes,RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Airplane}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</UserControl>

How can I bind a control into a WPF listview column

I'm new to WPF so forgive me if I've missed something obvious
I have the following list view control (non relevent details removed for brevity)
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Details}"/>
</GridView>
</ListView.View>
</ListView>
I add items to this list view in code behind
public void Add(LogEntry entry)
{
ListViewItem item = new ListViewItem();
item.Content = entry;
listView.Items.Add(item);
}
where LogEntry has public string properties for "Type" and "Details"
So far, so good, all works lovely but..
I want my details property to be an element itself (TextBox or DockPanel containing various types of content)
How can I bind this Element to the list column?
Using the code above, Changing "Details" to an element I simply get the class name in the list box (e.g. System.Windows.Control.TextBox) as by default the cell displays the ToString() value of the property.
I have googled examples which use a DataTemplate but I can't find an example of binding an element to the content of a panel.
The control cannot be defined in xaml as its structure and contents are not known until runtime
The binding is one way (I only need to display the list, not update the underlying data structure)
To make my problem clearer, I want to bind the following list item
class LogEntry
{
public string Type {get;}
public DockPanel Details {get;} // This dock panel was created by code and contains
// various elements not predictable at compile time
}
Your Model, the LogEntry class, should not reference a UI control. Instead it should contain the data needed by the UI, and the XAML should define a DataTemplate that uses that data. For example,
public class LogEntry
{
public string Type {get;}
public ObservableCollection<IDetail> Details {get;}
}
<DataGridTemplateColumn Header="Details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<ItemsControl ItemsSource="{Binding Details}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You mentioned that the DockPanel in the LogEntry created items that were not known at runtime. Can you give an example of that? In most cases, you should have some kind of pattern in the data, and you can use DataTemplates to define how to draw each Detail piece.
<DataTemplate DataType="{x:Type local:LoginDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding MachineLoggedIn}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:LogoutDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding LoggedInTime}" />
</StackPanel>
</DataTemplate>
If you REALLY need to store a control in the Model, then you can use a ContentControl in your DataGridTemplateColumn and set it's Content equal to the Details
<ContentControl Content="{Binding Details}" />
Sorry I cant give you an exact answer as I dont have VS where I am right now here but a few pointers.
First instead of using your method to add Listview items you want to create an ObservableCollection with your data. Then you can bind the itemssource of your listview to the observableCollection.
Next you can create an itemtemplate for the listview containing the control you want, something quite simple would be like a stack panel with horizontal orientation and two textboxes.
Once you have done that because you have set the itemsource of the listview to the ObservableCollection you can just bind the textbox to the String property within your collection.
Note that ObservableCollection is better to bind to than List as ObservableCollection supports NotifyPropertyChanged().
I have copied your code into a new project and created the same list view here is the code. And this one worked and displayed to data correctly
XAML:
<ListView x:Name="MyList" ItemsSource="{Binding MyListDetails}">
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Lastname}"/>
</GridView>
</ListView.View>
ViewModel:
private List<Contact> _details= new List<Contact>();;
public List<Contact> MyListDetails
{
get
{
return _details;
}
set
{
_details = value;
this.RaisePropertyChanged("MyListDetails");
}
}
public void AddEntry(LogEntry entry)
{
MyListDetails.Add(entry);
}

Resources