More Binding To ListView - wpf

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

Related

WPF GridView different stringformat for each item on gridviewcolumn binding

I'm trying to bind the StringFormat of a column binding depending on each datacontext item's individually.
Here's the sample code:
xaml:
<ListView ItemsSource="{Binding Symbols}">
<ListView.View>
<GridView x:Name="gw">
<GridView.Columns>
<GridViewColumn Header="Symbol" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price, StringFormat={Binding StringFormat}}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
code behind:
public ObservableCollection<symbol> Symbols { get;set;}
public class symbol : INotifyPropertyChanged
{
#region INotify Handler
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
private double _price;
public double Price
{
get => _price;
set
{
_price = value;
OnPropertyChanged(nameof(Price));
}
}
private int _decimalplaces;
public int decimalplaces
{
get => _decimalplaces;
set
{
_decimalplaces = value;
OnPropertyChanged(nameof(decimalplaces));
if (value == 0)
StringFormat = "0";//no decimal
else
StringFormat = $"0.{new string('0', value)}";//like 0.000 for 3 decimal places
}
}
public string StringFormat { get; set; }
}
The StringFormat={Binding StringFormat} is not possible, I've just put it there to demonstrate what I exactly wanted. Each item's (symbol) format is different.
It doesn't matter if I need to add the columns in code behind, I can do it but I just don't know how to.
Any suggestions? Thank you.
Update:
There is a solution I didn't want to use but now I believe it's the only logical way.
private double _price;
public double Price
{
get => _price;
set
{
_price = value;
OnPropertyChanged(nameof(Price));
StringPrice = value.ToString(format);
}
}
private string _stringprice;
public string StringPrice
{
get => _stringprice;
set {
_stringprice = value;
OnPropertyChanged(nameof(StringPrice));
}
}
and using it in XAML:
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding StringPrice}" />

Binding data of celltemple

<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Tên Mặt Hàng" CellTemplate="{StaticResource Ten}">
<GridViewColumn.CellTemple>
<DataTemplate x:Key="Ten">
<TextBlock Text="{Binding Ten}"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemple>
</GridViewColumn> </GridView>
every one, I have code, I want to binding Text of textBlock to use data again in other files, so what can I do?
You can create a ViewModel of this view. And bind the viewModel property to this view. For example:
public class NamViewModel: INotifyPropertyChanged {
private string _ten;
public string Ten {
get
{
return _ten;
}
set
{
_ten = value;
this.RaisePropertyChanged("Ten");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can bind this viewModel in the constructor of the view. For example:
public partial class NamView : Window {
public NamView() {
InitializeComponent();
this.DataContext = new NamViewModel();
}
}

Binding a String to a richtextbox

I have a datagrid populated with "notes" and when a note is clicked I want the richtextbox to show the note.comments. But the Bindings isn't working.
public NoteDTO SelectedNote {get; set;}
public string stringNotes {get; set;}
public void OpenNote()
{
stringNotes = SelectedNote.Comments;
}
<DataGrid x:Name="NoteGrid" cal:Message.Attach="[Event MouseDoubleClick] = [Action OpenNote()]" ItemsSource="{Binding Notes}" SelectedItem="{Binding SelectedNote}"
<toolkit:RichTextBox Text="{Binding stringNotes, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
If I may get help please.
The main problem is that you're binding to a property that has no concept of change notifications; you're not implementing INotifyPropertyChanged. That being said, why not just bind the RichTextBox directly to the property off of NoteDTO:
<toolkit:RichTextBox Text="{Binding SelectedNote.Comments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The other option is to manually copy the comments between SelectedNote and stringNotes, then implement INotifyPropertyChanged, but this isn't ideal unless you want to have an intermediate property before propagating them to the NoteDTO object.
EDIT:
I noticed that your SelectedNote property will never notify the UI that it has changed, which will prevent bindings from working. Try something like the following:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string selectedNote;
public string SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
this.OnPropertyChanged("SelectedNote");
}
}
}

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="+" />

WPF ListView setting SelectedItem

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...

Resources