WPF ListView setting SelectedItem - wpf

I've tried to search for an answer to this but I'm not having any luck. Basically I have a listview that is bound to a collection returned from a view model. I bind the selected item of the list view to a property in my listview in order to perform validation to ensure that an item is selected. The problem is that sometimes I want to load this listview with one of the items already selected. I was hoping to be able to set the property on my view model with the object I want selected and have it automatically select that item. This is not happening. My listview loads without an item selected. I can successfully set the selected index to the 0th index so why shouldn't I be able to set the selected value. The list view is in single selection mode.
Here's the pertinent code from my list view
<ListView Name="listView1" ItemsSource="{Binding Path=AvailableStyles}" SelectionMode="Single">
<ListView.SelectedItem>
<Binding Path="SelectedStyle" ValidatesOnDataErrors="True" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" BindingGroupName="StyleBinding" >
</Binding>
</ListView.SelectedItem>
<ListView.View>
<GridView>
<GridViewColumn Header="StyleImage">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="800.jpg"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Style Code" DisplayMemberBinding="{Binding StyleCode}"/>
<GridViewColumn Header="Style Name" DisplayMemberBinding="{Binding StyleName}"/>
</GridView>
</ListView.View>
</ListView>
And here is the pertinent code from my view model
public class StyleChooserController : BaseController, IDataErrorInfo, INotifyPropertyChanged
{
private IList<Style> availableStyles;
private Style selectedStyle;
public IList<Style> AvailableStyles
{
get { return availableStyles; }
set
{
if (value == availableStyles)
return;
availableStyles = value;
OnPropertyChanged("AvailableStyles");
}
}
public Style SelectedStyle
{
get { return selectedStyle; }
set
{
//if (value == selectedStyle)
// return;
selectedStyle = value;
OnPropertyChanged("SelectedStyle");
}
}
public StyleChooserController()
{
AvailableStyles = StyleService.GetStyleByVenue(1);
if (ApplicationContext.CurrentStyle != null)
{
SelectedStyle = ApplicationContext.CurrentStyle;
}
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
string error = string.Empty;
if (columnName == "SelectedStyle")
{
if (SelectedStyle == null)
{
error = "required";
}
}
return error;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
I should note that the "Style" referenced here has nothign to do with WPF. It's a business object. I'm really looking for a solution that doesn't break the MVVM pattern, but I'd be willing to just get something functioning. I've attempted to loop through the Listview.Items list just to set it manually but it's always empty when I try. Any help is appreciated.
Edit: I updated the code to use INotifyPropertyChanged. It's still not working. Any other suggestions
2nd Edit: I added UpdateSourceTrigger="PropertyChanged". That still did not work.
Thanks

Your problem is most likely caused because your SelectedItem Style is a different Style instance than the matching one in the AvailableStyles in the ItemsSource.
What you need to do is provide your specific definition of equality in your Style class:
public class Style: IEquatable<Style>
{
public string StyleCode { get; set; }
public string StyleName { get; set; }
public virtual bool Equals(Style other)
{
return this.StyleCode == other.StyleCode;
}
public override bool Equals(object obj)
{
return Equals(obj as Style);
}
}

Hmm... it looks like you forgot to implement INotifyPropertyChanged for the SelectedStyle property...

Related

How to programmatically add multiple strings to one line in a multiple column ListView

So I'm new to WPF as I'm more familiar with WinForms, But for the sake of drawing performance and good looking UI I switched to WPF, I have no experience in XAML but I'm working my things out.
I have a ListView which works as a playlist for my Media Player App. Adding Multiple Data in one line of multiple columns wasn't a problem in WinForms, I just had to add a ListViewItem and fill it's SubItems , but in WPF it's a problem, the ListViewItem doesn't have SubItems property nor the ListView, I tried multiple questions from Stack Overflow and other website which didn't help me , and it was all about DisplayMemberBinding but I still can't / don't know how to reference it in my code.
XAML for ListView:
<ListView x:Name="Playlist_Main" Margin="0" ItemsSource="{Binding SourceCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="#" DisplayMemberBinding="{Binding Num}"/>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}"/>
<GridViewColumn Header="Album" DisplayMemberBinding="{Binding Album}"/>
<GridViewColumn Header="Year" DisplayMemberBinding="{Binding Year}"/>
<GridViewColumn Header="Track Num" DisplayMemberBinding="{Binding Track}"/>
</GridView>
</ListView.View>
</ListView>
Main Code
Playlist_Main.Items.Add(New ListViewItem({Playlist_Main.Items.Count + 1, Info(0), Info(1), Info(2), Info(3), Info(4)}))
The bindings in your ListView / GridView require an item type that exposes properties for Num, Title, and so on. You have to expose a collection of that item type and assign or bind it to the ItemsSource property.
Create a model for an item in your playlist. The following example implements the INotifyPropertyChanged interface that enables bindings to update through the PropertyChanged event if a property value changes. If your properties are read-only or you do not need to update values at runtime, you do not have to implement it.
public class PlaylistItem : INotifyPropertyChanged
{
private int _num;
private string _title;
private string _artist;
private string _album;
private int _year;
private int _track;
public PlaylistItem(int num, string title, string artist, string album, int year, int track)
{
Num = num;
Title = title;
Artist = artist;
Album = album;
Year = year;
Track = track;
}
public int Num
{
get => _num;
set
{
if (_num == value)
return;
_num = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
if (_title == value)
return;
_title = value;
OnPropertyChanged();
}
}
public string Artist
{
get => _artist;
set
{
if (_artist == value)
return;
_artist = value;
OnPropertyChanged();
}
}
public string Album
{
get => _album;
set
{
if (_album == value)
return;
_album = value;
OnPropertyChanged();
}
}
public int Year
{
get => _year;
set
{
if (_year == value)
return;
_year = value;
OnPropertyChanged();
}
}
public int Track
{
get => _track;
set
{
if (_track == value)
return;
_track = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In a code-behind approach, you could create a collection of PlaylistItems. Use an ObservableCollection<T> that implements the INotifyCollectionChanged interface, if you want to reflect changes to the collection in the user interface, e.g. adding or removing items. The ObservableCollection<T> does that automatically through the CollectionChanged event. If your list is not modified at runtime, you can use any other collection.
var playlistItems = new ObservableCollection<PlaylistItem>();
playlistItems.Add(new PlaylistItem(1, "Enter Sandman", "Metallica", "Metallica", 1991, 1));
// ...add other playlist items.
You could assign this collection directly to the ListView e.g. in the constructor.
public MainWindow()
{
var playlistItems = // ...create the items collection or load them from somewhere.
Playlist_Main.ItemsSource = playlistItems;
}
A different approach is creating a public property in your code-behind. I assume it is the MainWindow.
public partial class MainWindow
{
public MainWindow()
{
PlaylistItems = // ...create the items collection or load them from somewhere.
}
public ObservableCollection<PlaylistItem> PlaylistItems { get; }
}
You would bind this collection in XAML using a RelativeSource binding to the window.
<ListView ItemsSource="{Binding Tracks, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
As you can see, there are multiple approaches, even more. The best, in my opinion, would be to use the MVVM pattern. For that, you would create a view model for your main window that contains the collection.
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
PlaylistItems = // ...create the items collection or load them from somewhere.
}
public ObservableCollection<PlaylistItem> PlaylistItems { get; }
// ...other properties and methods.
}
Next you would set an instance of this view model as DataContext of your window.
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
Then you can bind to the collection like this. The data context (MainViewModel) is inherited automatically.
<ListView ItemsSource="{Binding PlaylistItems}">
This pattern helps you to separate the user interface from the data and business logic. As you can see, there are no references from the view model to the view, only properties that expose data that can be bound.
Further resources for learning:
Data binding overview in WPF
How to: Create and Bind to an ObservableCollection

More Binding To ListView

Original question was here.
Binding To A ListView
I have fixed one issue and now see the column names. However, I can't figure out the binding.
The error from the output window:
System.Windows.Data Error: 4 : Cannot find source for binding with reference
'ElementName=This'. BindingExpression:Path=LogView.LogEntries; DataItem=null; target
element is 'ListView' (Name='LoggingListView'); target property is 'ItemsSource' (type 'IEnumerable')
Snippet of the XAML with my latest attempt from LogFileWindow.XAML. I can post more but trying to keep the clutter down:
<ListView Name="LoggingListView" ItemsSource="{Binding ElementName=This, Path=LogView.LogEntries} ">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=Date}"></GridViewColumn>
<GridViewColumn Header="Time" DisplayMemberBinding="{Binding Path=Time}"></GridViewColumn>
<GridViewColumn Header="Event" DisplayMemberBinding="{Binding Path=Event}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
The C# ViewModel:
public class LogEntryViewModel : INotifyPropertyChanged
{
public LogEntryViewModel(LogFileEntry le)
{
_date = le.Date;
_time = le.Time;
_event = le.Event;
}
#region Members
private string _date;
public string Date
{
get { return _date; }
set {_date = value;
RaisePropertyChanged("Date");
}
}
private string _time;
public string Time
{
get { return _time; }
set
{
_time = value;
RaisePropertyChanged("Time");
}
}
private string _event;
public string Event
{
get { return _event; }
set { _event = value;
RaisePropertyChanged("Event");
}
}
private LogFileEntry _le;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class LogViewModel :ObservableCollection<LogEntryViewModel>
{
private ObservableCollection<LogEntryViewModel> _LogEntries;
public ObservableCollection<LogEntryViewModel> LogEntries = new
ObservableCollection<LogEntryViewModel>();
}
Partial Class declaration and code-behind where I am trying to use it:
public partial class LogFileWindow : Window
{
public LogViewModel LogView = new LogViewModel();
}
The Visual Studio error is pretty self-evident and self explanatory: you're trying to do Binding with ElementName and searching for a visual element (supposedly defined in your XAML visual tree) with Name="This". There's no such thing (apparently, I couldn't tell because you didn't post the complete XAML tree).
If you want to bind a Visual Element Property to another Property in the same element you have you use RelativeSource Self

How to capture a button press in a ListView

I'm displaying a list of items in a WPF ListView, the items have a Quantity, Order Code and a Description. The columns are bound to fields in an ObservableCollection held in the View Model. This is all very standard and works as would expect. However, in the Quantity Column of the ListView I am adding two button + and -, the idea being that when they are pressed the value of the quantity either increments or decrements. The problem is that because these buttons are not bound to a field in the ObservableCollection I cannot get a link from the button being pressed in the List View to the record in the ObservableCollection. I have tried getting the item selected in the ListView but it is the button that gets selected when pressed and not the ListView item, I have also captured the item beneath the mouse pointer when the button is pressed but it could be pressed using the keyboard.
I feel there must be a (simple!) way of doing this but I can't find it.
This is the XAML:
<ListViewName="AccessoriesContent" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Select">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="QuantityStack">
<Button Name="SubtractAccessoryButton" Command="vx:DataCommands.SubtractAccessory" Content="-" />
<TextBox Name="QuantityTextBox" Text="{Binding Quantity, Mode=TwoWay}" />
<Button Name="AddAccessoryButton" Command="vx:DataCommands.AddAccessory" Content="+" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Order Code" DisplayMemberBinding="{Binding OrderCode}" />
<GridViewColumn Header="Description" DisplayMemberBinding="{Binding Description}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
The code behind:
public MainWindow()
{
//CommandBindings.Add(
InitializeComponent();
AccessoryVM = new AccessoryViewModel();
AccessoriesContent.ItemsSource = AccessoryVM.AccessoryCollection;
}
And the ViewModel:
class AccessoryViewModel
{
ObservableCollection<AccessoryData> _AccessoryCollection =
new ObservableCollection<AccessoryData>();
public ObservableCollection<AccessoryData> AccessoryCollection
{ get { return _AccessoryCollection; } }
public void PopulateAccessories(string order_code)
{
// Read the data and populate AccessoryCollection
}
}
public class AccessoryData : INotifyPropertyChanged
{
private int _quantity;
public int Quantity
{
get { return _quantity; }
set
{
this._quantity = value;
Notify("Quantity");
}
}
public string OrderCode { get; set; }
public string Description { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Beyond this I have two methods SubtractAccessory and AddAccessory which are triggered by the buttons but I have yet to populate them with anything that would work.
Another option is to create a RelayCommand (see here). In this model you create an ICommand property on each of your items. You then set this property to a new RelayCommand that accepts a delegate you would like to be ran when that command is activated. So this could be a QuantityUp method and a QuantityDown method on your AccessoryData. Once you've got your ICommand property in place you simply bind to it like this, where QuantityUpCommand is your ICommand property.
<GridViewColumn Header="" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Height="15" Width="15" Content="+" Command="{Binding QuantityUpCommand}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The AccessoryData would look something like this
private RelayCommand _quantityUpCommand;
public ICommand QuantityUpCommand
{
get
{
if (_quantityUpCommand == null)
{
_quantityUpCommand = new RelayCommand(QuantityUp);
}
return _quantityUpCommand;
}
}
public void QuantityUp(object obj)
{
Quantity++;
}
And RelayCommand looks something like this:
public class RelayCommand: ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
You do not appear to have posted the commands you use.
Anyway, if you do use commands you could either use instance commands which exist on the view model (you then will need to bind the command to the command property on the DataContext) and hence have access to the Quantity or you can pass the view model as CommandParameter just setting it to {Binding}, then in the command you can cast the parameter to the VM and change the Quantity.
(If you were to use the Click event you could just cast the sender to Button and cast its DataContext to the VM)
You could pass the current item through a CommandParameter on the button that uniquely identifies the current item. So that in the execution of the Command you know what item you're talking about. If you can't find a unique token in your item, you could even pass the whole item!
<Button Name="AddAccessoryButton" Command="vx:DataCommands.AddAccessory" CommandParameter="{Binding}" Content="+" />

Selected Value Combo Box not binding

Ok so I have spent hours now trying to figure this out and I cant.
I have the below combo box which is binding correctly to my collection of data.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValuePath="AnswerId"
SelectedItem="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer"/>
The Selected Item however is not populating top my Selected Answer property. I put a textbox on the form and bound it to SelectedAnswer.Answer and that is binding to the answer correctly.
For some reason though my combo box will not bind the selected answer
I have read something about the layout of the combo box property and tried changing that, also stepped through the getter and setter of the property to ensure it is not clearing down (which is not as it will bind to the text box)
Please help with this.
SurveyAnswer:
public class SurveyAnswer : INotifyPropertyChanged
{
private Guid answerId;
public Guid AnswerId
{
get { return answerId; }
set {
answerId = value;
NotifyPropertyChanged("AnswerId");
}
}
private string answer;
public string Answer
{
get { return answer; }
set {
answer = value;
NotifyPropertyChanged("Answer");
}
}
public Guid SurveyLineID { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set {
isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
#region NotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
I think you need to change SelectedItem to SelectedValue. Sometimes that order of parameters matters as well.
<ComboBox Name="cbx" Width="250" Height="25"
Visibility="{Binding Path=IsComboBox,Converter={StaticResource BoolConverter}}"
ItemsSource="{Binding Path=Answers}"
SelectedValue="{Binding Path=SelectedAnswer, Mode=TwoWay}"
DisplayMemberPath="Answer" SelectedValuePath="AnswerId"/>
This is helpful:
http://johnpapa.net/binding-to-silverlight-combobox-and-using-selectedvalue-selectedvaluepath-and-displaymemberpath

WPF Notify changes on object

I have a gridview were I define some columns, like this...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProp}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
I bind my gridview to a collection and implemts INotifyPropertyChanged in the property MyProp. This works well and any changes of MyProp are reflected to the gridview.
If I add another column that is bound to the object itself I dont get any notifications/updates. My code...
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
I think I need something like INotifyPropertyChanged for the object but I have no idea how to do this. Any suggestions?
Yes, the actual instance itself never changes - only its properties.
Presumably your converter relies on a bunch of properties from the object you've bound to? If so, you could use a MultiBinding and change your converter to an IMultiValueConverter. Then you can bind to all the dependent properties that might cause the TextBlock to update.
Make the object impletment the interface INotifyPropertyChanged
Here is an example from MSDN
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerName = String.Empty;
private string companyNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerName = "no data";
companyNameValue = "no data";
phoneNumberValue = "no data";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CompanyName
{
get {return this.companyNameValue;}
set
{
if (value != this.companyNameValue)
{
this.companyNameValue = value;
NotifyPropertyChanged("CompanyName");
}
}
}
public string PhoneNumber
{
get { return this.phoneNumberValue; }
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged("PhoneNumber");
}
}
}
}

Resources