I am having an issue binding an observable collection of viewmodels to a ListView.
Theres something interesting however which allows me to bind successfully which shows the code is correct ( as far as I can see )
The scenario:
I have 1 main screen which uses 'MainViewModel'
This mainviewmodel has a ListView which binds to a 'OpenSaleViewModel' which in turn has an observable collection of 'OpenSaleItemViewModel' objects.
The code I have in XAML works fine : The DisplayMemberBinding works fine
<Grid x:Name="SalesScreenHolder" Background="AliceBlue" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="70" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="grdSalesWindow" Margin="0,0,0,0" Grid.Row="0" Width="300" Height="350" MaxHeight="1000" DataContext="{Binding Main, Source={StaticResource MainVM}}">
<ScrollViewer x:Name="salesScrollViewer" PanningMode="VerticalOnly" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" >
<ListView x:Name="lstviewSales" ItemContainerStyle="{StaticResource alternatingListViewItemStyle}" AlternationCount="2" HorizontalAlignment="Left" HorizontalContentAlignment="Left" ItemsSource="{Binding OpenSale.OpenSalesItems, UpdateSourceTrigger=PropertyChanged}">
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Height" Value="30" />
</Style>
</ListView.ItemContainerStyle>-->
<ListView.View>
<GridView x:Name="listViewGrid">
<GridViewColumn Width="150" Header="Item"
DisplayMemberBinding="{Binding SaleItemName}" />
<GridViewColumn Width="50" Header="Qty"
DisplayMemberBinding="{Binding Quantity}" />
<GridViewColumn Width="90" Header="TotalPrice"
DisplayMemberBinding="{Binding TotalAmtString}" />
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
Now say I have 4 OpenSaleViewModel objects, 1 for each sale and each has its own collection of SalesItems
I try and bind the collection to a dialog window i open elsewhere in the application and it only shows me the namespace and the name of the viewmodel : see screenshot
I have tried using path=PROPERTYNAME and Data.PROPERTYNAME
The datacontext IS set as I am accessing properties in the parent viewmodel and binding successfully as you can see in the 3 labels above the grid. I have debugged and can see that the correct amount of items are picked up for each sale but the data is not recnoginized.
The thing is if i set 'ItemsSource="{Binding OpenSale.OpenSalesItems' to be 'ItemsSource="{Binding OpenSale' just to test what happens in the MAIN Screen, this means there is no data to bind against, so it seems that when the observable collection is null in the Main Screen, my dialog window does display the data ok - so i cant understand why 2 different viewmodels with different collections ( using the same class as the basis of the collection though ) seems to be conflicting??
It seems I can only bind when only 1 window is using a collection of the same type of viewmodel ( in this case 'SaleItems' within the sale viewmodel )
This is the code for my dialog window
<Grid x:Name="grdTableSalesItemsxxc" Margin="2,0,2,0" Grid.Row="0" Width="610" Height="200" MaxHeight="1000" >
<ScrollViewer x:Name="tableSalesScrollViewer" PanningMode="VerticalOnly" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" >
<ListView x:Name="lstviewTableSalexxc" ItemContainerStyle="{StaticResource alternatingListViewItemStyle}" AlternationCount="2"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Left" ItemsSource="{Binding Sale.OpenSalesItems, BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged}" >
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Height" Value="30" />
</Style>
</ListView.ItemContainerStyle>-->
<ListView.View>
<GridView x:Name="grdTableSaleGridxxc">
<GridViewColumn Width="295" Header="Item Name"
DisplayMemberBinding="{Binding SaleItemName}" />
<!--<GridViewColumn Width="100" Header="Quantity"
DisplayMemberBinding="{Binding SaleItemName}" />
<GridViewColumn Width="105" Header="Total Price"
DisplayMemberBinding="{Binding SaleItemName}" />-->
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
This is the collection from the parent viewmodel 'OpenSaleViewModel' which binds first time in the main screen
public ObservableCollection<OpenSaleItemViewModel> OpenSalesItems
{
get
{
if (_salesItems == null)
{
_salesItems = new ObservableCollection<OpenSaleItemViewModel>();
foreach (OpenSaleItem itm in _openSale.OpenSaleItems)
{
// Check if we need to grab the stockPLU or RecipeProduct PLU
if (itm.StockPLU != null)
{
_osi = new OpenSaleItemViewModel(itm.StockPLU);
}
else
{
_osi = new OpenSaleItemViewModel(itm.RecipeProductID);
}
// Populate remaining properties
_osi.Quantity = itm.Quantity;
_osi.TotalAmount = itm.TotalAmount;
_osi.SalesUnitCostPrice = itm.SalesUnitCostPrice;
if (itm.OpenSale != null)
_osi.OpenSaleID = itm.OpenSale.ID;
_salesItems.Add(_osi);
}
}
return _salesItems;
}
set
{
_salesItems = value;
RaisePropertyChanged("OpenSalesItems");
RaisePropertyChanged("TotalVAT");
RaisePropertyChanged("TotalAmtIncVAT");
}
}
I feel like your ItemsSource should be set on your GridView an not on your ListView. Did that not work for you?
Related
This question already has answers here:
ComboBox ItemsSource changed => SelectedItem is ruined
(8 answers)
Closed 4 years ago.
I have following Situation.
I have a ListView set up as following:
<ListView Margin="3,3,3,3" ItemsSource="{Binding Toner}" SelectedValue="{Binding CurrentToner}" IsSynchronizedWithCurrentItem="True">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="BorderBrush" Value="LightGray" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Id}" FontWeight="Bold"></Label>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" FontWeight="Bold"></Label>
<Label Grid.Row="1" Grid.Column="0" Content="{Binding Ratio}">
</Label>
<Label Grid.Row="1" Grid.Column="1" Content="{Binding TonerType.Name}" ></Label>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I set the Property CurrentToner in the corresponding ViewModel:
private Toner currentToner = new Toner();
public Toner CurrentToner
{
get => currentToner;
set
{
SetProperty(ref currentToner, value);
updateTonerCommand.RaiseCanExecuteChanged();
deleteTonerCommand.RaiseCanExecuteChanged();
increaseAmountCommand.RaiseCanExecuteChanged();
RaisePropertyChanged(nameof(Changes));
}
}
Now in the same View as above i also have some Controls bound to Properties of the CurrentToner.
<TextBox Style="{StaticResource TextBoxStyle}" Grid.Row="0" Grid.Column="1" Text="{Binding CurrentToner.Name}"></TextBox>
<TextBox Style="{StaticResource TextBoxStyle}" Grid.Row="1" Grid.Column="1" Text="{Binding CurrentToner.Level}" ></TextBox>
<ComboBox Grid.Row="2" Grid.Column="1" SelectedValue="{Binding CurrentToner.TonerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding TonerTypes, Mode=OneWay}" DisplayMemberPath="Name"></ComboBox>
<TextBox Style="{StaticResource TextBoxStyle}" Grid.Column="1" Grid.Row="3" Text="{Binding CurrentToner.Note}" AcceptsReturn="True" VerticalScrollBarVisibility="Visible"></TextBox>
Now i can click arround in the ListView and my TextControls will be updated as they are supposed to.
However, if i reload the Data in the Observable Collection, the selected Item will no longer be updated in the Combobox (The data is still available, i checked with the Debugger and the Combobox can still set the TonerType in the CurrentToner)
I reload the Data from an SQL Server using Entity Framework :
private void ReloadToner()
{
Toner.Clear();
using (var uw = new UnitOfWork(new Gritly_DEVEntities1()))
{
Toner.AddRange(uw.Toners.GetAll());
}
}
How do i have to implement my Reload logic to update the selected item again? (It works before the reload)
EDIT: Screenshots of how it is supposed to be and how it's not.
it should be like this
not like this
EDIT2: I found the Answer, I had to override Equals() on TonerType to compare based on Id instead of Adress
If you reload the data then you have new objects in your collection.
I missed what that object type is if it's there but let's call that a Toner anyhow.
Make sure Toner has something uniquely identifies it, like an ID.
If you look at a Toner object , you can probably work out which is the old current one but computers are very literal minded. They need you to tell them exactly how they're supposed to know a Toner is a specific Toner rather than just any old one.
When you're going to re-read your data, save that ID in a private variable.
Read the data.
Build your observable collection.
Find the appropriate entry using some linq matching on id.
Something like:
Toner newSelectedToner = Toners.Where(x=>x.ID = lastSelectedID).FirstOrDefault();
That's just air code by the way.
Set CurrentToner to that instance.
Also, override equals on your Toner to use ID.
I have an object that looks like:
List =
Parent+Child,
Parent+Child
Parent+Child
Parent+List<Child>
Parent+Child
Parent+Child have been flattened out to aid binding whilst I get this working.
I am trying to write them to a templated list box, using a different template for if there is 1 child, and another template if there is more than 1 child. I have got this working, but I want to bind the Multiple Children to a TreeView, so you can expand out the Parent and see all the Children, but I am struggling to get this to bind.
Here is my code (in logical order):
<ListBox x:Name="lsbGrandParent" ItemTemplateSelector="{StaticResource dataTemplateSelector}" />
<localutilities:dataTemplateSelector
OneChildTemplate="{StaticResource dtOneChildSelected}"
MultipleChildrenTemplate="{StaticResource dtMultipleChildrenSelected}"
x:Key="dataTemplateSelector"/>
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
int childCount = Convert.ToInt32(item).Count;
if (childCount <= 1)
return OneChildTemplate;
else
return MultipleChildrenTemplate;
}
<DataTemplate x:Name="dtMultipleChildrenSelected" x:Key="dtMultipleChildrenSelected">
<Grid HorizontalAlignment="Left" VerticalAlignment="Center">
<StackPanel Orientation="Vertical" >
<TreeView HorizontalAlignment="Left" ItemTemplate="{StaticResource menuMultipleChildren}" Width="220" Height="100" Margin="0,0,0,0" MaxHeight="220" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
</StackPanel>
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="menuMultipleChildren" ItemsSource="{Binding Path=MultipleChildren}" ItemTemplate="{StaticResource menuChildren}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=ParentId}" Width="20" Height="Auto" />
<Label Content="{Binding Path=ParentName}" Width="140" Height="Auto" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="menuChildren">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom" >
<Label Content="{Binding Path=ChildName}" Width="140" Height="Auto" />
</StackPanel>
</HierarchicalDataTemplate>
But when it runs there is just a space where the treeview should be, and I have put a break on when the ParentName binds and it is not binding. If I put the template code directly in the TreeViewItem it binds but does not display the value.
Am I doing something obviously wrong, or is there a better way to achieve what Im trying to achieve?
I've just started to learn binding in WPF and am having some trouble with using multiple ObjectDataProviders with the same control.
I have two ObjectDataProviders :
Is used to get a list of customer locations from a database and is used to populate a TreeView and
Takes a location as a parameter and returns all the customers from that location, populating a listView.
I'd like to make it so that when I click on one of the TreeView items, that it would take the SelectedItem text as the parameter, use it to populate the listview.
<ObjectDataProvider
x:Key="getLocations"
ObjectType="{x:Type local:DataSetCreator}"
MethodName="getLocations"
/>
<ObjectDataProvider
x:Key="getCustomersFromLocation"
ObjectType="{x:Type local:DataSetCreator}"
MethodName="getCustomersFromLocation">
<ObjectDataProvider.MethodParameters>
<x:Static Member="System:String.Empty" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<TreeView HorizontalAlignment="Left"
Margin="12,12,0,12"
Name="treeView2" Width="186"
ItemsSource="{Binding Source={StaticResource getLocations}}" >
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListView x:Name="lstCustomers"
ItemsSource="{Binding Source={StaticResource getCustomersFromLocation}}" Margin="204,41,12,12">
<ListView.View>
<GridView>
<GridViewColumn Header="CustomerID"
Width="200"
DisplayMemberBinding="{Binding Path=CustomerID}" />
<GridViewColumn Header="Company Name"
Width="370"
DisplayMemberBinding="{Binding Path=CompanyName}" />
</GridView>
</ListView.View>
</ListView>
Is it possible to achieve this within the XAML, or do I need to use the code-behind?
ObjectDataProviders are not very flexible as they cannot be bound. Among other things you could bind to the SelectedItem of the TreeView and employ a Binding.Converter to get you the items right items based on that value.
I am using a view model to bind to the list view. Every time I add an item in the view model internal observable collection, I trigger an LastIndex property with the list.Count-1. The list view is bound to this LastIndex proeprty of VM and the listview correctly selects the last item added to the view. Unfortunately, the view is not able to scroll the last added item into view.
I tried setting IsSynchronizedWithCurrentItem = "True" on list view markup, but it did not help.
This is the markup I am using
<ListView ItemsSource="{Binding Path=Status.Messages}"
SelectedIndex="{Binding Path=Status.LastIndex, Mode=OneWay}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Auto"
HorizontalAlignment="Stretch"
Height="60"
IsSynchronizedWithCurrentItem="True" >
<ListView.Resources>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False" >
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" FontWeight="Thin" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
<ListView.
</ListView>
Any help in this regard will be greatly appreciated
You need to call ScrollIntoView:
list.ScrollIntoView(list.Items[list.Items.Count - 1]);
http://msdn.microsoft.com/en-us/library/system.windows.controls.listbox.scrollintoview.aspx
EDIT:
And here's a way to do it in XAML:
http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/
I got a problem trying to find an element declared in DataTemplate, that after was applied like a ContentTemplate to TabItem object.
I saw that there is already some solutions in regard of this problem, but no one of them actually works in my case, and I would like to understand why (obviously I make mistake in some place)
Here is a sample code:
<DataTemplate x:Key="TabItemDataTemplate">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Name="templateGrid">
<Grid.RowDefinitions>
<RowDefinition Height="6.0*"> </RowDefinition>
<RowDefinition Height="6" ></RowDefinition>
<RowDefinition Height="6.0*" ></RowDefinition>
<RowDefinition Height="*" ></RowDefinition>
</Grid.RowDefinitions>
<ListView x:Name="repoView" Grid.Row="0"
VerticalAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource DataProviderForListView}}">
<GridView>
<GridViewColumn Header="State"
DisplayMemberBinding="{Binding Path=RepositoryItemState}"/>
<GridViewColumn Header="Working Copy Rev num."
DisplayMemberBinding="{Binding Path=WCRevision}"/>
<GridViewColumn Header="Repository Rev num."
DisplayMemberBinding="{Binding Path=RepoRevision}"/>
<GridViewColumn Header="User"
DisplayMemberBinding="{Binding Path=Account}"/>
<GridViewColumn Header="Item"
DisplayMemberBinding="{Binding Path=ItemName}"/>
</GridView>
</ListView>
<GridSplitter x:Name="gridSplitter" Grid.Row="1"
ResizeDirection="Rows" Background="Gray"
Height="4" HorizontalAlignment="Stretch"
Style="{StaticResource gridSplitterStyle}"/>
<RichTextBox x:Name="rowView" Grid.Row="2"
BorderBrush="Bisque" VerticalAlignment="Stretch"
IsReadOnly="True" Background="YellowGreen"
FontFamily="Comic Sans Serif"/>
<ToggleButton x:Name="rbWorkingCopy"
Template="{StaticResource ToggleButtonControlTemplate}"
Grid.Row="3" Width="100" Height="22"
Content="{StaticResource WorkingCopyTitle}"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
Command="repoManager:AppCommands.GetWorkingCopyInfoCommand" />
<ToggleButton x:Name="rbRepository"
Template="{StaticResource ToggleButtonControlTemplate}"
Grid.Row="3" Width="100" Height="22"
Content="{StaticResource RepositoryTitle}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom" Margin="120,0,0,0"
Command="repoManager:AppCommands.GetRepoInfoCommand" />
<ProgressBar x:Name="checkRepositoryProgress" Grid.Row="3"
Width="220" Height="22" HorizontalAlignment="Right"
VerticalAlignment="Bottom" Margin="250,0,10,0"
IsIndeterminate="True"
IsEnabled="{Binding repoManager:ExecutingCommand}" />
</Grid>
</DataTemplate>
This code is porgrammatically applied to the given TabItem object in following way :
this.ContentTemplate = FindResource("TabItemDataTemplate") as DataTemplate;
After I need access to the ListView element declared in DataTemplate, so I execute the codes found around in internet, and also on this site. Here is a short example:
/* Getting the ContentPresenter of myListBoxItem*/
ContentPresenter myContentPresenter =
FindVisualChild<ContentPresenter>(this);
// this.GetVisualChild(0)
/* Finding textBlock from the DataTemplate that is set on that ContentPresenter*/
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
ListView repoListView = (ListView)myDataTemplate.FindName("repoView",
myContentPresenter);
Problem1: In this case ContentTemplate of ContentPresenter is Null, so code execution crashes.
Prolem2: Ok, I think, may be I need to navigate throw TabItem content directly, so the code becomes, more or less:
/* Getting the ContentPresenter of myListBoxItem*/
ContentPresenter myContentPresenter =
FindVisualChild<ContentPresenter>(this);
// this.GetVisualChild(0)
/* Finding textBlock from the DataTemplate that is set on that ContentPresenter*/
DataTemplate myDataTemplate = this.ContentTemplate;
ListView repoListView = (ListView)myDataTemplate.FindName("repoView",
myContentPresenter);
this is TabItem object. But the strage things, that the ContentTemplate of this is completely different from that one assigned above. I'm sure that I missed something somewhere, can you help me to figure out the problem ?
Thank you.
You don't want to use any of the template properties of the TabItem, since those are used to create the actual controls, rather than storing them. You should be able to search the visual tree for the ListView directly, rather than going through the DataTemplate.
Ok, here we come :)
I resolve the problem, in not very nice way, but it seems that works correctly.
As I mentioned above I used LoadContent method and it returns me the ListView object, but by the way it wasn't the ListView that UI actually uses. So to resolve that problem I add static property to hold my REAL ListView object (static as I have single DataTemplate that contains ListView shared across multiple TabItems, so the ListView shared too) and add event handler to my DataTemplate -> Loaded. Catching this event, that in my case raises only ones in lifetime of application, in RoutedEvent's OriginalSource I got the REAL ListView object that WPF engine uses for rendering on UI.
Hope my solution will help someone.
Thank you all.
Simply, if you have a DataGrid, and a TemplateColumn which contains a data template, you can use the following code sample:
<DataGridTemplateColumn x:Name="photoPathColumn" Header="{x:Static resx:FrmResource.Photo}" Width="Auto">
<DataGridTemplateColumn.CellEditingTemplate x:Uid="keyelm">
<DataTemplate x:Name="dodo">
<StackPanel Orientation="Horizontal" Height="Auto">
<TextBlock x:Name="photo" x:Uid="imageFile" Text="{Binding Path=PhotoPath}"></TextBlock>
<Button x:Name="Browse" Content="..." Click="Browse_Click"></Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
TextBlock tBlock = (TextBlok)photoPathColumn.CellEditingTemplate.FindName(
"photo",
photoPathColumn.GetCellContent(CustomersDataGrid.CurrentItem));
Where photo is the name of text block
Where photoPathColumn is the DataGrid's TemplateColumn.