I have a Grid control defined in WPF...
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
Further down, I have defined an ItemsControl...
<ItemsControl Name="EntitlementsList" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Entitlements, Mode=TwoWay}"
Margin="0 10 0 3" AlternationCount="2">
<ItemsControl.Template>
...
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type Model:Entitlement}">
<Grid Margin="0 0 10 0" >
<Grid.Style>
...
</Grid.Style>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="0"
HorizontalAlignment="Left" Margin="0 2 0 0"
VerticalAlignment="Bottom"
Style="{DynamicResource NotesToggleButton}"
CommandParameter="{Binding}"
Command="{Binding Path=DataContext.GetEntitlementDetails,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" />
...
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This displays my Entitlements correctly.
The command for the ToggleButton is defined as...
private RelayCommand _getEntitlementDetails;
public RelayCommand GetEntitlementDetails
{
get
{
return _getEntitlementDetails ?? (_getEntitlementDetails = new RelayCommand(x =>
{
CurrentEntitlement = x as Entitlement;
}));
}
}
Then I have another ItemsControl that is collapsed until CurrentEntitlement is set...
<ItemsControl Name="ProductList" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="5" Height="150"
HorizontalAlignment="Stretch" BorderBrush="Transparent"
ItemsSource="{Binding ContextManager.CurrentContext.CurrentEntitlement.ProductList, Mode=TwoWay}"
Visibility="{Binding ContextManager.CurrentContext.CurrentEntitlement,
Converter={StaticResource NullVisibilityConverter}}">
<ItemsControl.Template>
...
</ItemsControl>
My item source is ProductList inside the Entitlements class...
private ObservableCollection<Product> _productList;
public ObservableCollection<Product> ProductList
{
get { return _productList; }
set
{
_productList = value;
OnPropertyChanged();
}
}
...where all the properties of "Product" raise OnPropertyChanged.
Now, I have been fully expecting my button to execute the command to set the CurrentEntitlement, make my second ItemsControl visible within the grid and display the ProductList, and stepping through the code shows that CurrentEntitlement does get set with a valid instance with items in the ProductList...but I still see nothing on the UI. I have been searching for a solution for 3 days, and I apologize if this is a duplicate question, but I am at my wit's end!
I can't see the issue. What am I doing wrong here?
Visual Studio 2015 Community, targeting 4.5, running on Windows 7 Enterprise SP1.
Thanks in advance!
I ended up finally figuring it out. There was a binding to some data that was written incorrectly that invalidated the second ItemsControl.
Related
I have a WPF application that searches through a large data set and displays the results in a ListView. The search can return a small result set, or the result set can be thousands of items. Searching the data set and returning the result set takes less than a second. The return set is an ObservableCollection. My ListView is slow when it's being loaded. The ListView is bound to the ObservableCollection in the XAML. This is the XAML:
<GroupBox Header="Translations" Grid.Row="2" Margin="10,0,10,8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView Grid.ColumnSpan="2"
ItemsSource="{Binding FoundItems}"
SelectionMode="Single"
MaxHeight="2000"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True">
<ListView.View>
<GridView>
<GridViewColumn Header="Translation File Name" Width="NaN" DisplayMemberBinding="{Binding Path=FileName}" />
<GridViewColumn Header="English" Width="400" DisplayMemberBinding="{Binding Path=English}" />
<GridViewColumn Header="International" Width="400" DisplayMemberBinding="{Binding Path=International}" />
</GridView>
</ListView.View>
</ListView>
<Border Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{DynamicResource AccentColorBrush}">
<Grid Margin="5,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<RadioButton VerticalAlignment="Center" Content="Search English" IsChecked="{Binding Path=SearchEnglish}" />
<RadioButton VerticalAlignment="Center" Grid.Column="1" Content="Search International" IsChecked="{Binding Path=SearchInternational}" />
</Grid>
</Border>
</Grid>
</GroupBox>
This is the view model property to which the ListView is bound:
public ObservableCollection<DataAccess.TranslationItem> FoundItems
{
get
{
return p_FoundItems;
}
set
{
p_FoundItems = value;
NotifyOfPropertyChange("FoundItems");
TranslationsFound = string.Format("{0} translations found", p_FoundItems.Count);
}
}
This is the code that builds the FoundItems collection. It takes just a few ms to execute and build the collection. The speed issue is definitely not located here. It is using a black box DLL to get the list. It then builds a collection of items that can be used in the ObservableCollection.
public List<TranslationItem> SearchList(bool fCaseSensitive, bool fIgnoreAmpersands, bool fExactMatch,
string sSearchLanguage, string sSearchString)
{
List<TTranslations.TranslationItem> lstFound = null;
List<TranslationItem> lstReturn = new List<TranslationItem>();
p_trItems.SearchingFile += p_trItems_SearchingFile;
lstFound = p_trItems.SearchList(fCaseSensitive, fIgnoreAmpersands, fExactMatch, sSearchLanguage, sSearchString);
foreach (TTranslations.TranslationItem tiItem in lstFound)
lstReturn.Add(new TranslationItem(tiItem));
return lstReturn;
}
In the view model, I have the following code that is responsible for setting FoundItems. It casts the collection returned to an ObservableCollection.
FoundItems = new ObservableCollection<DataAccess.TranslationItem>(p_trItems.SearchList(p_fCaseSensitive, p_fIgnoreAmpersands, p_fExactMatch, p_fSearchEnglish ? "E" : "I", p_sSearchString));
A breakpoint before and after this line of code indicates that FoundItems takes mere milliseconds to build.
Any idea why it would be loading so slowly? This is a conversion from an older application where I'm building the list manually, in a loop, and that takes only a second or two load.
This ended up being an easy fix. I added the following to the ListView declaration:
ScrollViewer.CanContentScroll="True"
It now looks like this:
<ListView Grid.ColumnSpan="2"
ItemsSource="{Binding FoundItems}"
SelectionMode="Single"
MaxHeight="2000"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.IsVirtualizing="True"
ScrollViewer.CanContentScroll="True">
My list went from taking at least a minute to load large lists, to almost instantaneous.
I have a ListView defined in a XAML interface which is bound to a collection.
The list view's DataTemplate features a WebView. I need this WebView to size to its contents, which means that after the ListView is bound, and elements are created, and those elements are bound, I need to execute a fragment of code on each WebView.
I've already taken care of getting the WebView to bind, and I have the code which will size a WebView. I simply need to know how to execute it; where to put it; how to get the WebViews and when to try to get them.
EDIT:
Here's my list view.
<ListView Opacity="{Binding IsRefreshing, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToGhost}}" Grid.Row="0" Name="listView" ItemsSource="{Binding Messages}" IsItemClickEnabled="False" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="2" Grid.RowSpan="2" Fill="White" RadiusX="12" RadiusY="12" />
<TextBlock Foreground="#FFAAAAAA" Grid.Row="0" Grid.Column="0" Margin="8" Text="You" HorizontalAlignment="Left" FontSize="11" Visibility="{Binding IsFromStaff, Mode=OneTime,Converter={StaticResource BooleanToInvisibility}}" />
<TextBlock Foreground="#FFAAAAAA" Grid.Row="0" Grid.Column="0" Margin="8" Text="Staff" HorizontalAlignment="Left" FontSize="11" Visibility="{Binding IsFromStaff, Mode=OneTime,Converter={StaticResource BooleanToVisibility}}" />
<TextBlock Foreground="#FFAAAAAA" Grid.Row="0" Grid.Column="1" Margin="8" Text="{Binding Timestamp, Mode=OneTime}" HorizontalAlignment="Right" FontSize="11" />
<WebView local:MyProperties.HtmlString="{Binding Body}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="8" ScrollViewer.VerticalScrollBarVisibility="Disabled" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding ShowCaseCommand, Mode=OneWay}" CommandParameter="{Binding ElementName=listView, Path=SelectedItem}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ListView>
The solution is something called "Behaviors".
We see in the code snippet that the DataTemplate contains a WebView, which is the item I want to affect. So I add a Behavior to the WebView, which is a class with code. The class looks like so:
public class WebViewSizeBehavior : DependencyObject, IBehavior
{
public DependencyObject AssociatedObject { get; private set; }
public void Attach(DependencyObject associatedObject)
{
var control = associatedObject as WebView;
if (control == null)
throw new ArgumentException(
"WebViewSizeBehavior can be attached only to WebView.");
AssociatedObject = associatedObject;
control.LoadCompleted += Control_LoadCompleted;
}
private void Control_LoadCompleted(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
var control = (WebView) AssociatedObject;
var resizeTask = control.ResizeToContent();
}
public void Detach()
{
var control = (WebView) AssociatedObject;
control.LoadCompleted -= Control_LoadCompleted;
AssociatedObject = null;
}
}
My namespaces and XAML are rigged up such that this class is reachable only the local namespace. How this is done is beyond the scope of this answer. So, given that, I can amend the XAML like so:
<ListView.ItemTemplate>
<DataTemplate>
…
<WebView …>
<interactivity:Interaction.Behaviors>
<local:WebViewSizeBehavior />
</interactivity:Interaction.Behaviors>
</WebView>
</DataTemplate>
</ListView.ItemTemplate>
I've left out content which is beyond the scope of this thread. Information about fitting a WebView to its content, as well as namespaces and XML namespaces, is available elsewhere.
I am creating a little application that has one feature to show list of people in popup when user click on button. That list, that is in popup, can be pretty big, it can have as many as 3k entries. I am using virtualization, and I don't have problem with performance when list is drawn. But, when user open application for the first time and click on button for popup with list of people, it can take 2-5 seconds before popup is shown. After, that if user try to open popup again popup will be open without delay.
So my question is could I say to ListView to prepare items while it is not shown. Because there is very big chance that user will use application for quiet some time before it will need this popup.
Can I optimize this in some other way? Is there some collection that is better for this purpose in WPF?
Also people collection will be populated when page is loaded, and popup is at beginning closed.
Here it is code that I have:
public class AddressBookViewModel : BaseViewModel
{
...
private ObservableCollection<PeopleModel> people;
...
public ObservableCollection<PeopleModel> People
{
get { return people; }
}
}
public class PeopleModel
{
public PeopleModel(string address, string name)
{
Address = address;
Name = name;
}
public string Name{ get; set; }
public string Address { get; set; }
}
<Button x:Name="btnChoosePerson"
Command="{Binding TogglePeopleAddressPopupCommand}"
Content="..." />
<Popup MaxHeight="520"
IsOpen="{Binding ShowPeopleAddressPopup}"
Placement="Bottom"
PlacementTarget="{Binding ElementName=btnChoosePerson}"
StaysOpen="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView
Grid.Row="2"
Width="500"
HorizontalAlignment="Left"
ItemsSource="{Binding People}"
SelectionMode="Single"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.CacheLength="10"
VirtualizingPanel.CacheLengthUnit="Item">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
HorizontalAlignment="Left"
Text="{Binding Address}" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Right"
Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Popup>
I am trying to get a custom UserControl to render in a ListBox, but nothing is being rendered. I came across this question and solution which works for the simple example, but my situation is a little different. I have a PersonControl for a Person object and a CoupleControl that can reference two PersonControl controls.
I've tried a couple things in the CoupleControl which haven't worked. I commented out one of the ways:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Control:PersonControl Grid.Column="0"
x:Name="LeftPerson" />
<Control:PersonControl Grid.Column="1"
x:Name="RightPerson" />
<!-- This is how I'd like to do it in case I create other controls
I wish to replace the PersonControls (e.g. AnimalControl) -->
<!--<UserControl Grid.Column="0"
x:Name="LeftPerson" />-->
<!--<UserControl Grid.Column="1"
x:Name="RightPerson" />-->
</Grid>
The relevant WPF snippet for the list box:
<ListBox Grid.Row="1"
ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Control:CoupleControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the code-behind:
public ObservableCollection<CoupleControl> Persons { get; private set; }
Person joe = new Person("Joe", "Smith", Person.SexType.Male);
Person jane = new Person("Jane", "Smith", Person.SexType.Female);
PersonControl joeControl = new PersonControl();
PersonControl janeControl = new PersonControl();
joeControl.DataContext = joe;
janeControl.DataContext = jane;
CoupleControl coupleControl = new CoupleControl();
coupleControl.LeftPerson.DataContext = joe;
coupleControl.RightPerson.DataContext = jane;
//coupleControl.LeftPerson.Content = joeControl; // Also doesn't work
//coupleControl.RightPerson.Content = janeControl; // Also doesn't work
Persons.Add(coupleControl);
Can someone help me get the CoupleControl to render in a ListBox?
Your approach is a bit too code-heavy for my taste, why not set DataContext in XAML?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Control:PersonControl DataContext="{Binding LeftPerson}" />
<Control:PersonControl DataContext="{Binding RightPerson}" Grid.Column="1" />
</Grid>
Or maybe even drop UserControls altogether if they are not so complex? In this case using DataTemplates can be faster and simpler. Say we defined templates for Person and Couple in Window resources (Couple is just a class with LeftPerson and RightPerson properties):
<Window.Resources>
<DataTemplate x:Key="personTemplate" DataType="TestWPF:Person">
<Border BorderThickness="1" BorderBrush="Green" CornerRadius="5">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" Margin="3,0,0,0" />
</StackPanel>
</Border>
</DataTemplate>
<DataTemplate x:Key="coupleTemplate" DataType="TestWPF:Couple">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding LeftPerson}"
ContentTemplate="{StaticResource personTemplate}" />
<ContentControl Content="{Binding RightPerson}"
ContentTemplate="{StaticResource personTemplate}" Grid.Column="1" />
</Grid>
</DataTemplate>
</Window.Resources>
Then you set ItemTemplate for your ListBox:
<ListBox Grid.Row="1" ItemsSource="{Binding Persons}" ItemTemplate="{StaticResource coupleTemplate}" />
This way you can make some more templates for the types you need and just set them in ListBox in one single line.
I'm experiencing a strange problem with data-binding. In the designer everything is shown correctly, but at runtime my ListView just contains those TextBlocks with an empty string in them. I'm using MVVM light for ViewModels. Most of the code was generated by VS2012 by creating a split page for WindowsStore-Apps.
Here's my XAML: ( I already removed some other parts, like the VisualStateManager, but without effect so that doesn't seem to be an issue.)
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="Kunden.OrderPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Kunden"
xmlns:common="using:Kunden.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding SelectCustomersViewModel, Source={StaticResource Locator}}">
<Page.Resources>
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Items}"/>
</Page.Resources>
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="primaryColumn" Width="610"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="titlePanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button
x:Name="backButton"
Click="GoBack"
IsEnabled="{Binding DefaultViewModel.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Grid.Column="1" Text="Werbeauswahl" Style="{StaticResource PageHeaderTextStyle}" Margin="0,0,-2,40"/>
</Grid>
<!-- Elementliste mit vertikalem Bildlauf -->
<ListView
x:Name="itemListView"
AutomationProperties.AutomationId="ItemsListView"
AutomationProperties.Name="Items"
TabIndex="1"
Grid.Row="1"
Margin="-10,-10,0,0"
Padding="120,0,0,60"
IsSwipeEnabled="False"
SelectionChanged="ItemListView_SelectionChanged"
ItemsSource="{Binding AvaiableCountries}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
... closing brackets etc.
The IEnumerableI want to bind:
private IEnumerable<Country> avaiableCountries;
public IEnumerable<Country> AvaiableCountries
{
get { return avaiableCountries; }
set { avaiableCountries = value; RaisePropertyChanged(() => AvaiableCountries); }
}
The data value object CountryI need for my LINQ-Query:
public class Country
{
internal string Name { get; set; }
}
Please try try changing the access modifier of the Name property from internal to public and see whether that fixes your problem?
If that doesn't work post the code where you're populating AvaiableCountries with data