My data from an ObservableCollection only sometimes displays on my ListView. If I restart the app, the data displays fine. Sometimes when I navigate away from the page and go back, the data will sometimes display and other times not. It seems to be random.
Here is my XAML code:
<ScrollViewer Grid.Row="2" Margin="0,42,0,0">
<Grid>
<ListView ItemsSource="{x:Bind collection, Mode=OneWay}" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" IsItemClickEnabled="True" SelectionChanged="MySelectionChanged" Visibility="Visible">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ObjectName">
<TextBlock Text="{x:Bind Data0, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView ItemsSource="{x:Bind collection, Mode=OneWay}" HorizontalAlignment="Left" Margin="375,0,0,0" VerticalAlignment="Top" SelectionMode="None" Visibility="Visible">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:ObjectName">
<TextBlock Text="{x:Bind Data1, Mode=OneWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ScrollViewer>
I tried doing a Debug.WriteLine() on all the data that should be displayed, and the debug output successfully prints it every time. There's only a problem when it comes to displaying this data on the ListView.
EDIT: Here's the corresponding xaml.cs code:
private ObservableCollection<ObjectName> collection;
private List<ObjectName> sorted;
private ObjectName clicked = new ObjectName();
public MainPage()
{
GetSave();
this.InitializeComponent();
}
private async void GetSave()
{
DataStorage ds = new DataStorage();
await ds.DeserializeObjectAsync();
collection = ds.ObjectName;
if (collection != null)
{
sorted = new List<ObjectName>(collection);
sorted.Sort((x, y) => string.Compare(x.Data0, y.Data0));
collection = new ObservableCollection<ObjectName>(sorted);
}
}
I tried InitializeComponent() before and after the logic, and it gives the same result.
Based on the xaml.cs code you provided, the problem looks like this:
First, you are not awaiting an async call.
Second, you are replacing the ObservableCollection.
The solution:
Don't call async code from your page's constructor. Async calls are
called async for a reason and trying to 'hide' their true nature by
omitting the await keyword when calling them won't work. Change GetSave()'s
return value to Task, override the OnNavigatedTo() method (read more
about it in it's documentation) of MainPage and await GetSave()
inside that. This alone won't solve your problem though.
Never replace an ObservableCollection. I remember banging my head on the
wall multiple times because of this. What you need to understand is
that when you are binding to an object in XAML, a dedicated Binding
object is created that links the source (in your case, the
ObservableCollection) and the target (the ListView) together. In
your code, initially 'collection' is set to null. When your MainPage
is created, the Binding object is created as well and it binds that
null value to your ListViews' Source property. Later, when your
async initialzation code is finished, you replace that null value
with an actual ObservableCollection, but the ListViews are not
notified about that, they are only looking for changes in the
collection's items that they are bound to, they are not prepared for
handling the situation when the collection itself is swapped
under them. So what you can do to fix this problem is: only create
your ObservableCollection instance in MainPage's constructor (or at
declaration - matter of taste in this case) and in GetSave() first
call collection.Clear() and then add your items to it with
collection.Add().
What currently happening in your code is that in some cases you are replacing the ObservableCollection before the Binding object is created and sometimes after that, so that's why it looks like you app's behavior is totally random - because actually it is. :) By adding the modifications I suggested above, you'll make sure that the Binding object is bound to the ObservableCollection you created in the constructor (empty at the time of the binding), and then you are initializing that collection after MainPage is already loaded, so your ListViews are getting notified about the changes.
Related
I tried to follow the example here:
WPF ListBox with self-removing items
It made sense but my issue was, the ListView itself is determining the template used. So it can easily customise the bindings to point to the correct target. I am however using MVVM and am struggling to fit the two together.
Example, if the template was:
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
This suddenly becomes more difficult, as ideally, I want to reuse that view without hard coding the bindings.
I tried to use DependencyProperty to pass the List and the Element through, so I could delete it via command.
<ListBox.ItemTemplate Name="myList">
<DataTemplate>
<local:MyItemView TheList={Binding ElementName=myList, Path=DataContext.List} TheElement={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
However, I had binding errors telling me that it couldn't convert the value for TheElement from MyClassViewModel to MyClass. Even if I commented that out TheList was always NULL.
Essentially I want:
class MyDataClass { // pretend there's more here}
class MyDataClassContainer
{
public ObservableCollection<MyDataClass> Items;
public void Add(MyDataClass);
public void Remove(MyDataClass);
}
class MyDataClassEntryViewModel
{
public static readonly DependencyProperty ListItemProperty = DependencyProperty.Register("TheClass", typeof(MyDataClass), typeof(MyDataClassEntryViewModel));
public static readonly DependencyProperty ListContainerProperty = DependencyProperty.Register("TheContainer", typeof(MyDataClassContainer), typeof(MyDataClassEntryViewModel));
public MyDataClass TheClass;
public MyDataClassContainer TheContainer;
public ICommand Delete = new DelegateCommand(RemoveItem);
private function RemoveItem(object parameter)
{
TheContainer.Remove(TheClass);
}
}
With the following templates:
MyDataClassEntryView.xaml
<UserControl>
<Grid>
<Button Content="Delete" Command="{Binding Path=Delete}"/>
</Grid>
</UserControl>
MyDataContainerView.xaml
<UserControl>
<ListView x:Name="listView" ItemsSource="{Binding Path=Container.Items}">
<ListView.ItemTemplate>
<DataTemplate>
<local:MyDataClassEntryView TheClass="{Binding}" TheContainer="{Binding ElementName=listView, Path=DataContext.Container}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
Note: I have omitted most of the superfluous lines, as I'm trying to get a generic answer I can use everywhere. Not a hard coded single solution. I was basically want to keep the MVVM structure strong, without lots of hard coded and wiring in the background. I want to use the XAML as much as possible.
All the other methods I see to do with removing from a list, require all sorts of assumptions, such as using the SelectedIndex/Item, or using a method on the ContainerView itself to take the element as a parameter, cast it, then remove, etc. In short, most solutions are far too hard coded to the given examples. It feels like there should be an easy way to achieve this in WPF.
As the ListView issautomatically creating instances of my sub-ViewModel/Views, it's impossible for me to get any data in apparently. I just want to pass parameters along using bindings, basically.
Your button should look like this:
<Button Content="Delete"
Command="{Binding Path=Delete}"
CommandParameter="{Binding}/>
Then the remove command should look something like this:
private function RemoveItem(object parameter)
{
var item = parameter as MyDataClass
if(item != null)
TheContainer.Remove(item);
}
You do not need to pass the list to the UserControl within the ItemTemplate, since it doesn't need to know about the list at all
Edit:
I read over your question a few times to see what you were confused about so I will try to clarify.
Whether the ListView sets its own template in the Xaml, or you use another UserControl, the datacontext still gets passed down to the item. Regardless of how you decide to template the items, the ItemTemplate will have the datacontext of a single item from the ListView's items list.
I think your confusion comes in with having controls outside being brought in for templating. Think of it as if the Xaml from the control you brought in being cut and pasted into the DataTemplate of the ListView when running the program, and then it is really no different from being hard coded in there.
You cannot reach outside of a DataTemplate with Element bindings like you have tried.
Instead you need to use a relativesource like this.
<local:MyItemView TheList="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=DataContext.List}" />
Im getting this error while trying to giva my treeview an itemsource
"Items collection must be empty before using ItemsSource."
I have checked a lot of solutions and I cant seem to find a way to solve this. Here are my code snippets:
XAML:
<HierarchicalDataTemplate x:Key="Category">
<TextBlock Text="{Binding Name}">
</TextBlock>
</HierarchicalDataTemplate>
XAML
<telerik:RadTreeView x:Name="treeview" IsDragDropEnabled="True" HorizontalAlignment="Left" Height="250" Margin="10,10,0,-3" VerticalAlignment="Top" Width="190" IsManipulationEnabled="True" IsLoadOnDemandEnabled="True" LoadOnDemand="treeview_LoadOnDemand" IsExpandOnSingleClickEnabled="True" ItemTemplate="{StaticResource Category}">
</telerik:RadTreeView>
C# - Giving the treeview a data source:
Data d = new Data();
treeview.ItemsSource = d.Get_Categories();
C# - My database query:
public List<Category> Get_Categories()
{
using (var context = new ProcessDatabaseEntities())
{
return context.Category.ToList();
}
}
Category only has two properties, Name and ID. I know that the itemsource-list is not empty when I assign it. So it's probably something wrong with my XAML-code. Thank you in advance!
I believe that your problem is a common one. Basically, you cannot use both the TreeView.ItemsSource and the TreeView.Items properties together... you must choose one way or the other. Usually this problem manifests itself because a developer has done something like this...:
<TreeView Name="TreeView" ItemsSource="{Binding SomeCollection}" ... />
... and then tried to do something like this in the code behind:
TreeView.Items.Add(someItem);
The solution in that case would be to manipulate the data bound collection instead of the TreeView.Items collection:
SomeCollection.Add(someItem);
However, in your case (and it's a little bit difficult to guess without seeing your code), you have probably done the second part first (set or manipulated the Items property) and then tried to set the ItemsSource property. Your solution is the same... use one method of editing the items or the other... not both.
I'm working on a digital signage WPF application and the basic structure is setup as follows:
I have a single view with a grid in it bound to a playlist object. The playlist contains panes, the panes contain playlist items, and the playlist items each contain a content item. I use DataTemplates to build the view for each piece, e.g.
<!-- This template represents a single content pane, no real display here, just empty space with a height and width -->
<DataTemplate DataType="{x:Type entities:ContentPane}">
<ContentControl
Content="{Binding CurrentPlaylistItem, Mode=OneWay}"
Height="{Binding Height, Mode=OneTime}"
Width="{Binding Width, Mode=OneTime}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
/>
</DataTemplate>
<!-- This is a playlist item which is contained within a ContentPane.
This also has no real display, just a placeholder for the content item, and will likely contain some transition information-->
<DataTemplate DataType="{x:Type entities:PlaylistItem}">
<inf:TransitionableContentControl Content="{Binding ContentItem}"></inf:TransitionableContentControl>
</DataTemplate>
<!-- image item template
the path can be any valid uri e.g. http://... or file://... -->
<DataTemplate DataType="{x:Type contentTypes:ImageContentType}">
<Grid Background="Transparent" x:Name="ImageType">
<Image Source="{Binding Bitmap, Mode=OneTime}"></Image>
<inf:BusinessAd
ContainerHeight="{Binding ImageHeight, Mode=OneTime}"
ContainerWidth="{Binding ImageWidth, Mode=OneTime}"
Visibility="{Binding AdText, Converter={StaticResource _VisibilityConverter}, Mode=OneWay}"
Text="{Binding AdText.Text, Mode=OneTime}"
AdFontSize="{Binding AdText.TextStyle.FontSize}"
AdFontFamily="{Binding AdText.TextStyle.FontFamily}">
<ContentControl
Content="{Binding AdText, Mode=OneTime}"
ContentTemplate="{StaticResource BusinessAdTextTemplate}">
</ContentControl>
</inf:BusinessAd>
</Grid>
</DataTemplate>
As the datacontext changes, the content in each pane transitions from one item to the next. I'm running into a problem where the user is placing the same item in a pane twice in a row, be it a video, image, etc. What happens is the change goes undetected and the UI does not update. In the case of a video, it freezes on the last frame of the first video and the whole application hangs.
I have tried doing an OnPropertyChanged("ContentItem") within the PlaylistItem class, tried setting Shared="False" on my data templates, I tried changing properties on the ContentItem object and raising PropertyChanged events, nothing seems to work. I turned on tracing on the databinding to see what was happening and it all appears to be working correctly. When I change properties on the ContentItem it shows a new hash for the new item, but no change on the UI.
Within the TransitionableContentControl in the PlaylistItem, the OnContentChanged override is never hit when going from one content item to the same one. If I swap that control out with a regular ContentControl, no change.
Former Employee is correct about the diagnosis. Furthermore, unless you manage to re-assign the inner datacontexts your templates will not restart from the beginning, as you have noticed. You will have to assign your CurrentPlaylistItem to some default empty item first to reset stuff all the way through. To avoid races and nasty flicker, do it on a dispatcher priority higher than UI rendering. Try this:
// CurrentPlaylistItem = pli ; -- not good
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// clear item
// null doesn't work, because TransitionableContentControl's
// inner ContentControl would be empty and the brush created
// from it would be a null brush (no visual effect)
CurrentPlaylistItem = new PlaylistItem () ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// set new item, possibly same as old item
// but WPF will have forgotten that because
// data bindings caused by the assignment above
// have already been queued at the same DataBind
// level and will execute before this assignment
CurrentPlaylistItem = pli ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
}), DispatcherPriority.DataBind) ;
})) ;
The issue here is the ContentControl (or rather the underlying DependencyProperty framework) doesn't fire OnContentChanged when .Equals is true for old and new Content. One workaround is to have your own dependency property and transition on CoerceValue. I'll email you the code and you can post it here.
I have a combobox in my MainWindow.xaml file like so:
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
I've assigned the datacontext in the codebehind using this.datacontext = this.
I created a ViewProperties that is accessed as a property in the MainWindow and is a class that implements INotifyPropertyChanged and contains the MaterialDropDownValues as a property.
I even changed the the MaterialDropDownValues to be an ObservableCollection.
The problem is that the databinding works on initialisation however if the MaterialDropDownValues property is changed the combobox values are not updated.
I have the following in the ViewProperties class:
public ObservableCollection<string> MaterialDropDownValues
{
get { return this.materialDropDownValues; }
set
{
this.materialDropDownValues = value;
OnPropertyChanged("MaterialDropDownValues");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
Any ideas why this is not working? All the other answers I could find advised to implement INotifyPropertyChanged and make the property an observablecollection.
Solution 1:
Dont recreate this.materialDropDownValues try to do
this.materialDropDownValues.Clear();
foreach(var mystring in myStrings)
this.materialDropDownValues.Add(mystring);
for all new items. If this doesnt work then try solution 2...
Solution 2:
As per my experience, I think ObservableCollection of primitive types like int, string, bool, double etc. does not refresh on Property Change notification if ItemsControl.ItemTemplate is not specified.
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
ItemsSource="{Binding ViewProperties.MaterialDropDownValues}"
SelectedValue="{Binding ViewProperties.Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type System:String}">
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This is because the itemscontrol's items container creates non-observable item containers in it for primitive data by simply copying item.ToString(). In the code above the {Binding} should update the data changes when the whole items source is changed.
Let me know if this works.
When I bump into things like this, the first thing I do is play around with the binding mode. Something like:
ItemsSource="{Binding Path=ViewProperties.MaterialDropDownValues, Mode=TwoWay}"
That sometimes trips you up. The other thing I would make sure of is that if you're instantiating new ViewProperties object(s) following your initial load, you notify change on that. If you don't, the XAML will be referring to an outdated version of the object while your code behind/view model is operating on a different one.
Edit in response to comments
None of the below solved the problem, but is left as a reference.
Original Answer
The problem is that you have not specified the DataContext for your view, which is where WPF looks for Binding values by default.
Provided that your ViewProperties property on MainWindow is public you can simply change your binding to:
ItemsSource="{Binding ViewProperties.MaterialDropDownValues,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}"
This causes WPF to look for the property value on the first occurence of Window that it finds above the combobox in the visual tree.
Alternatively, you can just set the Window.DataContext property to your instance of ViewProperties and change the binding to the following:
ItemsSource="{Binding MaterialDropDownValues}"
Either of these will work, but I would suggest using the latter as it is closer to the MVVM pattern that is generally preferred in WPF/XAML applications.
what happens if you change your xaml to
<ComboBox Name="material1ComboBox"
HorizontalAlignment="Center"
MinWidth="100"
DataContext="{Binding ViewProperties}"
ItemsSource="{Binding MaterialDropDownValues}"
SelectedValue="{Binding Material1SelectedValue}"
SelectionChanged="Material1ComboBoxSelectionChanged">
</ComboBox>
nevertheless you should just instantiate your collection once and just use remove, add and clear when you use a OberservableCollection.
Posting this in case anyone else runs into this. I came up this as the best search result matching my symptoms, but it turns our that none of the answers worked above for me.
I was using WinUI3 and apparently it uses the newer x:Bind syntax for it's XAML. Apparently x:Bind defaults it's Mode to OneTime which is why it wouldn't update after the first value (I also tried Binding but couldn't get that to work)
From: <TextBlock Text="{x:Bind MyField}" x:Phase="1" Margin="0,5,0,5"/>
To: <TextBlock Text="{x:Bind MyField, Mode=OneWay}" x:Phase="1" Margin="0,5,0,5"/>
So if you are using x:Bind, make sure set Mode=OneWay AND implement INotifyPropertyChanged and then things should work
Update: I've updated the code based on your help so far, and still no luck. When the application loads the ListBox has no items. I assign junk values to Customers in the windows's contructor, and then am also trying to set the ListBox's DataContext as follows:
CustomerList.DataContext = Customers;
--- Original Question (with updated code) ---
I'm having trouble with databinding in a WPF project.
I have a class, Customer, as follows:
public class Customer
{
public String Name { get; set; }
public String Email { get; set; }
}
In my XAML's code behind I have a collection of customers as follows:
public List<Customer> Customers { get; set; }
I'm trying to bind each customer to a ListBox with a ListItemTemplate displaying the customer's information (name/email) in TextBoxes along with a button which locks/unloacks the TextBoxes (sets the IsEnabled property to true or false).
What's the best way to go about this?
So far I've been tryingt he following with no success.
In the XAML I currently have the following (ignoring the toggle part for now, I'm just trying to get the collection itself to be listed.):
<Window.Resources>
<CollectionViewSource x:Key="Customers" Source="{Binding Path=Customers, Mode=TwoWay}"/>
<DataTemplate x:Key="Customer">
<StackPanel Orientation="Horizontal">
<TextBox Content="{Binding Name}" />
<TextBox Content="{Binding Email}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource Customers}}"
ItemTemplate="{StaticResource ResourceKey=Customer}"
Name="CustomerList"
Height="300" />
</StackPanel>
You need to change
ItemsSource="{Binding Source=Customers}"
to
ItemsSource="{Binding Source={StaticResource Customers}}" DataContext="{StaticResource Customers}"
Code similar to the updated one works for me after changing
<TextBox Content="{Binding Name}" />
to
<TextBox Text="{Binding Name}" />
As TextBox doesn't have Content property(like a Label), the former refused to compile in VS.
Well, it is set to Text in definition:
[ContentPropertyAttribute("Text")]
public class TextBox : TextBoxBase, IAddChild
But I thought it is only used between the brackets(<TextBox>Like so</TextBox>)?
Could this be the source of the problem?
Try setting the ItemsSource of your CustomerList as follows: ItemsSource="{Binding}". You've set the DataContext of the ListBox to the list of customers, you need to set the ItemsSource to the same collection, hence, the direct binding.
Another thing that you can do, in case you prefer to use the CollectionViewSource, is to set the DataContext of your window to the same class DataContext=this, because without this, the resource definition won't be able to locate the "Customers" collection that you defined in the code behind. If you do this, however, you don't need CustomerList.DataContext = Customers; because you're directly assigning the ItemsSource to a static resource, not relatively to the DataContext.
One more thing. I think you should give the CollectionViewSource and the corresponding collection in the code behind different names. This isn't going to cause a runtime issue, but it makes it hard to maintain the code ;)
Hope this helps :)