Dynamically updating a WPF ListView after changes - wpf

HI
I have this code
private void Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<ListProduct> myList = new ObservableCollection<ListProduct>();
int index = myList.IndexOf((from parcour in myList
where parcour.name == myProduct.name
select parcour).FirstOrDefault());
if(index != -1)
{
myList[index].number++;
}
}
public class ListProduct
{
public string name { get; set; }
public int number{ get; set; }
}
XAML:
<ListView Name="ListView12" ItemsSource="{Binding}" Height="201">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="name"
DisplayMemberBinding="{Binding name}" />
<GridViewColumn Width="100" Header="number"
DisplayMemberBinding="{Binding nombre}" />
</GridView>
</ListView.View>
</ListView>
this snippet is to modify the number of occrnce of an element on myListView
when I click the button and made those change myListView do not change. Any Help please -- is something missing?

If your code is exactly as you show it, this is because you are creating a new ObservableCollection which is local to the Button_Click method, and letting it go out of scope. You need to either set the DataContext to the new collection, or (more idiomatic) modify the elements of the collection which is your existing DataContext.
If you adopt the first option, you'll need to fix the code, because the LINQ query is running over the new (empty) collection and will always return null.
If you're already doing the second option, or change you code to use the second option, you still have a problem because ListProduct is not raising property change notification. So when you modify the number property, WPF has no way of detecting this change and updating the binding.
To fix this, implement INotifyPropertyChanged on the ListProduct class, and update your property setters to raise the PropertyChanged event. (You will not be able to use C# automatic properties; you must implement the property getters and setters by hand.)

Related

How to bind in WPF with values upstream in hierarchy?

I have a list of items I wish to display in a ListView/GridView. Each item is a class/object containing a format and array of bytes. The format dictates how the bytes are to be displayed (hex or decimal). I am using a converter to go back and forth between TextBox.Text and the byte array.
The converter needs the format and string/array. I tried to use IValueConverter and pass the format as a ConverterParameter, but this didn't work since it is not a DependencyProperty. I tried to use IMultiValueConverter but that didn't work because I do not get the format in ConvertBack. I thought that if I could bind to the whole object (MyDataItem), then the converter would work fine. However, I cannot figure out how to bind to that. I tried a bunch of variations using RelativeSource and other properties, but couldn't figure it out. Can someone help me with the binding?
If there is a better way to accomplish my task, feel free to suggest it.
public enum FormatEnum
{
Decimal,
Hex
}
public class MyDataItem
{
public byte[] Data { get; set; }
public FormatEnum Format { get; set; }
}
public class ViewModel
{
ObservableCollection<MyDataItem> DataItems = new ObservableCollection<MyDataItem>();
}
XAML (with non-working binding)
<ListView ItemsSource="{Binding DataItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Format">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!--ComboBox for the format-->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Data">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path,
Converter={StaticResource ResourceKey=DataBytesConverter},
ConverterParameter={Binding Format}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Welcome to SO!
Just a heads-up for future....you'll have a much better chance of getting questions answered if you provide an MCVE. The more work you make people do to reproduce your exact problem, the less inclined they'll be to do so.
There are a couple of different ways of doing what you're trying to do, but the main problem is that the data you are binding to doesn't support INPC. I'll let you read up on that, but the problem is essentially that the left hand (your text display field) doesn't know what your right hand (the ComboBox) is doing. When the ComboBox changes the format it has to signal to anything that depends on it that the value has changed. That's where INPC comes in. You can implement it yourself if you like, and there are plenty of tutorials around the net showing how to do so, but it's much easier to just use an existing library like MVVM Light, which is what I'll use here.
Before I go into detail, I should point out that your idea of using an IMultiValueConverter would in fact work, provided you passed in Data and Format as separate parameters so that it would update whenever either of them changes value. Many people will actually suggest solutions like this, but it's not the best way of doing what you're trying to achieve. Converters and behaviors are really just extensions of the view, which is fine when they expose parts of the view that you otherwise can't get to. But in your case the problem is that the data you are providing the view isn't in a format that your view can readily consume, and in a "proper" WPF application, it should be. Fixing your data beforehand is usually faster, a lot easier to debug when it goes wrong, and it opens up the possibility for things like unit testing. We already know that your MyDataItem class has to be either modified or replaced in order to support INPC, so that's a good place to do your values-to-text logic.
So to start with, create a view model for your model object that exposes the properties you want to pass to your view laye (i.e. "Format") and adds a new one for the text string you want to display (i.e. "DataText). We'll also create an update function to fill that string in, INPC support and a tiny bit of update logic:
public class MyDataItemViewModel : ViewModelBase
{
private MyDataItem DataItem;
public MyDataItemViewModel(MyDataItem dataItem)
{
this.DataItem = dataItem;
UpdateDataText();
}
public FormatEnum Format
{
get { return this.DataItem.Format; }
set
{
if (this.DataItem.Format != value)
{
this.DataItem.Format = value;
RaisePropertyChanged(() => this.Format);
UpdateDataText();
}
}
}
private string _DataText;
public string DataText
{
get { return this._DataText; }
set
{
if (this._DataText != value)
{
this._DataText = value;
RaisePropertyChanged(() => this.DataText);
}
}
}
private void UpdateDataText()
{
switch (this.Format)
{
case FormatEnum.Decimal:
this.DataText = String.Join(", ", this.DataItem.Data.Select(val => String.Format("{0:D}", val)));
break;
case FormatEnum.Hex:
this.DataText = String.Join(", ", this.DataItem.Data.Select(val => String.Format("0x{0:X2}", val)));
break;
default:
this.DataText = String.Empty;
break;
}
}
}
Your "DataItems" collection needs to be public and accessible via a getter (something you didn't do in your original code), and it needs to be a collection of these view models instead:
public ObservableCollection<MyDataItemViewModel> DataItems { get; } = new ObservableCollection<MyDataItemViewModel>();
And now for each instance of MyDataItem you want to wrap it in an instance of MyDataItemViewModel:
this.DataItems.Add(new MyDataItemViewModel(new MyDataItem { Format = FormatEnum.Decimal, Data = new byte[] { 1, 2, 3 } }));
this.DataItems.Add(new MyDataItemViewModel(new MyDataItem { Format = FormatEnum.Decimal, Data = new byte[] { 4, 5, 6 } }));
Now your data is in a much better format. ObjectDataProvider provides a nice and convenient way to put all the values for a particular type of Enum in a single list that a ComboBox can bind to, so let's create one of those for your FormatEnum:
xmlns:system="clr-namespace:System;assembly=mscorlib"
<Window.Resources>
<ObjectDataProvider x:Key="FormatEnumValues" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:FormatEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
And your ListView can now bind directly to all this data without any converters or behaviors or anything messy like that:
<ListView ItemsSource="{Binding DataItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Format">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource FormatEnumValues}}" SelectedItem="{Binding Path=Format}" Width="100" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Data">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataText}" TextTrimming="CharacterEllipsis" MinWidth="100" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Result:

WPF Listview - Get row where property changed

I have a listview with objects that have int IDs and string names. The name column is a textbox that is editable by the user. If the user changes a name, I want to be able to search the list for other objects with that same ID and change those names as well.
My big question is, I want to use the LostFocus property of the textbox to get the entire row, or object, instead of just the textbox.
The XAML below is greatly simplified, but I think it gets the basic idea across:
<ListView x:Name="linkList"
<GridViewColumn Width="75">
<GridViewColumn.CellTemplate>
<TextBox Text="{Binding linkName}" LostFocus="TextBox_LostFocus"/>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="75">
<GridViewColumn.CellTemplate>
<TextBox Text="{Binding linkID}"/>
</GridViewColumn.CellTemplate>
</GridViewColumn>
So in code behind:
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
//sender is the textbox in this case. so how
//would I get the object in that particular row so I can get its ID#?
}
I need to identify that particular row since the user can either click on a different row or press "enter" to save the name change. So going by the currently selected row is no good. Any ideas?
Google for the GetVisualAncestor<T> extension and use that.
((TextBox)sender).GetVisualAncestor<ListViewItem>();
Pretty obvious now, but I just had to find the ancestor of the type of ListViewItem and I'm able to pull the info I need from that object.
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
ListViewItem changedRow = GetAncestorOfType<ListViewItem>(sender as TextBox);
//now I can get the info I need from the changedRow object!
}
public T GetAncestorOfType<T>(FrameworkElement child) where T : FrameworkElement
{
var parent = VisualTreeHelper.GetParent(child);
if (parent != null && !(parent is T))
return (T)GetAncestorOfType<T>((FrameworkElement)parent);
return (T)parent;
}

DataTemplateSelector and its weird behavior

I am using a DataTemplateSelector inside a ContentControl. I have 3 different DataTemplates based on 3 different object types. When I set the content of my ContentControl to data of the mentioned types, the DataTemplateSelector swaps to the specific DataTemplate AND the selector futhermore seems to rollback/reset the values from the old template. Why is that so?
Edit: I figured out that the values get resetted because I have an attached property caled Prop and inside its OnPropertyChangedCallback it notifies me about the Prop having value null on swapping between DataTemplates. You can see that attached property in code below.
Can somebody help me out what happens behind this swapping mechanism of DataTemplateSelector?
Here is a deeper explaination with code:
public void Window1()
{
InitalizeComponents();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if(this.DataContext == null)
this.DataContext = "Hallo";
else{
if(this.DataContext is string)
this.DataContext = 123;
else{
if(this.DataContext is int)
this.DataContext = null;
}
}
}
By clicking on Button few times I change the type and so in ContentControl the selector changes to DataTemplate.
The selector looks like this below. It swaps between textDataTemplate and numericDataTemplate and one more template. As I mentioned i have those three type which are string, int, and one more, that i wish not to metion. Their DataTemplates are called textDataTemplate, numericDataTemplate and that one more. :)
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate;
public DataTemplate NumericTemplate;
public DataTemplate Select(object item, Culture.....)
{
if(item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
ContentControl and XAML looks like this:
<Button Click="OnClick" Content="Click Me"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="123"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
And this is how textDataTemplate looks alike.
<DataTemplate x:Key="textDataTemplate">
<TextBox x:Name="text" my:AttProperties.Prop="{extension:MarkupExt value}" Text="{Binding Path=Txt, Mode=Default, UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
numericDataTemplate looks similar to textDataTemplate just that only digits are allowed.
The Prop is my attached property from AttProperties class of type string. The Prop is somewhere inside of all three DataTemplate. Above the Prop is sitting on a TextBox but it could be a Label too. The markupextension is just a "return Hello". The extension is just there to test how to create a custom markupextension. There is no big deal with the extension. It shouldnt have to do much with the swapping of DataTemplates.
One more time to explain my problem. Swapping seems reselts/rollback my old templates. I swap from textDataTemplate to lets say numericDataTemplate and the Prop of textDataTemplate gets set to null but the value before was "Hello".
Why is that happening? It seems like the same behavior with using tiggers. Once a Trigger is no more valid it rollsback the used values. Is a DataTemplateSelector using some kind of same mechanism as Triggers?
Edited:
The attached property is just a simple .RegisterAttached with an OnPropertyChangedCallback. Inside OnPropertyChangedCallback I figured the prop is null when swapping the dataTemplates.
If you use two-way binding in numeric template and it only accepts something like Double, it can set value to number. But no one can be sure without seeing full code. It's possible that your own code does something wrong.
To understand things better, create your own control, derived from the ContentControl, and use it in your sample. Then override control methods OnContentxxxChanged, insert breakpoints there and debug your application. You should understand, what's going on with your data and with template selector. When application stops on breakpoint, carefully check all values and look at stack trace. To debug bindings you can insert IValueConverters, it would give you place in code, where you can check values.
I really suggest you to make the simplest working thing first, and then go to more complicated things such as textboxes with two-way bindings to some property of some control which you didn't show in your question. Here is a working version with TextBlocks:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void OnClick(object sender, RoutedEventArgs e)
{
if (this.DataContext == null)
this.DataContext = "Hallo";
else if (this.DataContext is string)
this.DataContext = 123;
else if (this.DataContext is int)
this.DataContext = null;
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate {get; set;}
public DataTemplate NumericTemplate {get; set;}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is string)
{
return this.TextTemplate;
}
else
{
return this.NumericTemplate;
}
}
}
and xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="numericDataTemplate">
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
<DataTemplate x:Key="textDataTemplate">
<TextBlock Foreground="Green" Text="{Binding}"/>
</DataTemplate>
<local:MyTemplateSelector x:Key="dataTemplateSelector"
TextTemplate="{StaticResource textDataTemplate}"
NumericTemplate="{StaticResource numericDataTemplate}"/>
</Grid.Resources>
<StackPanel>
<Button Click="OnClick" Content="Click Me" VerticalAlignment="Top"/>
<ContentControl Name="contentCtrl"
Content="{Binding}"
Width="300" Height="100"
ContentTemplateSelector="{StaticResource dataTemplateSelector}" />
</StackPanel>
</Grid>
Compare with your code. When you inherit from DataTemplateSelector, you should override SelectTemplate method and don't invent methods with other names. All controls such as ContentControl will only use SelectTemplate. Etc..
Obviously, all works and DataTemplateSelector does nothing wrong. I suppose, your problem is somewhere in your data and bindings
And look at your OnClick method - it always sets DataContext to null

When to use Custom/User Control or Template/Style

I'm creating a logger application thing to learn WPF, and I want new messages to appear at the bottom of a scrollviewer every time some listener picks up a new message. I want each message to create something that looks like this:
==================================
= Source | Message =
= | =
= | =
==================================
I've got 2 WPF books here, and they both have "warnings" saying that there are other simpler and better mechanisms than custom/user controls in most cases. I was wondering if this can be solved using control templates, and if so, what control type should I use as a base?
Perhaps Binding can help. (It might overcomplicate things if you're still learning WPF though.)
With binding we can make the ListView only be the visible representation of your logging, meaning the logic to add/remove logs can stay separate from the ListView entirely.
class LogEntry
{
public string Source { get; set; }
public string Message { get; set; }
public LogEntry(string source, string message)
{
Source = source;
Message = message;
}
}
class Listener
{
private int m_maxLogs = 10;
private ObservableCollection<LogEntry> m_logEntries;
public ObservableCollection<LogEntry> LogEntries { get { return m_logEntries; } }
public Listener()
{
m_logEntries = new ObservableCollection<LogEntry>();
}
public void AddLogEntry(string source, string message)
{
if (LogEntries.Count >= m_maxLogs)
{
//Remove the oldest LogEntry.
LogEntries.RemoveAt(0);
}
LogEntries.Add(new LogEntry(source, message));
}
}
If the datacontext is set to the Listener instance the xaml becomes (based on the previous answer):
<ListView ItemsSource="{Binding LogEntries}">
<ListView.View>
<GridView>
<GridViewColumn Header="Source" Width="120" DisplayMemberBinding="{Binding Source}"/>
<GridViewColumn Header="Message" Width="400" DisplayMemberBinding="{Binding Message}"/>
</GridView>
</ListView.View>
</ListView>
If you want to dynamically change the text of your log entries for some reason, you'll need to implement the INotifyPropertyChanged interface in the LogEntry class to make the listview update.
Try using using a ListView and setting its View to a GridView.
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Source" Width="120" />
<GridViewColumn Header="Message" Width="400" />
</GridView>
</ListView.View>
</ListView>
Refer: How to: Display ListView Contents by Using a GridView

WPF ListView Databind not binding!

A simple WPF ListView:
<ListView Name="recordContainer" ItemsSource="{Binding Path=MyCollection}">
<GridView>
<GridViewColumn Width="260" Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Width="100" Header="Value" DisplayMemberBinding="{Binding Path=Value}"/>
</GridView>
</ListView>
MyCollection is a property on my Page:
public ObservableCollection<MyData> MyCollection
{
get
{
return myCollection;
}
}
and this is my data object:
public class MyData
{
public string Name { get; set; }
public string Value { get; set; }
}
I fill up myCollection in the contructor of the page (before InitializeComponent) but the list is coming up empty!
I have the exact same configuration on other pages and it works fine - what am I missing?
Any help appreciated!
Set the DataContext of the page to the page itself :
this.DataContext = this;
What Thomas said... or...
What you're missing is that the binding is actually examining the DataContext. You can set the source differently a number of ways, however.
Think of it this way... Where is the binding to look for MyCollection? Binding is just a class; it isn't all knowing. You have to tell it where to look. By default, this is the DataContext. The DataContext is shared among the logical tree elements of your UI, with items lower in the tree able to see DataContexts higher up in the tree, or even override this value for items lower than themselves.
In your case, you want a value located on your Page which is not the DataContext. You must tell Binding how to find this. You can do this via the RelativeSource property.
{Binding MyCollection RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}
This is a combination of three markup helpers, Binding, RelativeSource, and Type.
RelativeSource finds an object in the tree relative to the current position. In this case, we are Finding an Ancestor (we're looking up the tree). RelativeSource walks up the tree looking for the first object of type Page. Once it finds this, it returns it to Binding. Binding then examines this object for a property MyCollection.
Here: http://simplesample.site90.com/wpf_binded_listview.php
is a complete example showing a ListView binded to a ObservableCollections and manipulating them using Commands.
Hope this helps.
I believe the problem is that a listview takes as a container something like a gridview. This is how a listview allows for non-linear arrangements of items unlike a listbox.
Think about it as
ListView = Layout Coordinator
GridView = Layout Style
Elements = Items to be displayed
Unless you intend to display your items in anyway other than a list, use a ListBox.
(p.s. disregard highlighting)

Resources