I have a data-bound TreeView with a WindowsFormsHost in the data template of its items. The more items in the TreeView, and so the more WindowsFormsHost in it, the slower the UI becomes.
The TreeView is in a TabItem, itself in a TabControl. The sluggishness is most obvious whenever the TabItem is selected (ie when I switch from another TabItem to the TabItem with the TreeView).
To simplify, I made a simpler app with a ListBox instead of a TreeView:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl>
<TabItem Header="Tab 1">
<StackPanel>
<Button Content="Add 50 WindowsFormsHost controls"
Click="Button_Click" />
<ListBox Name="lst" Height="300">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Bar}" />
<WindowsFormsHost />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</TabItem>
<TabItem Header="Tab 2" />
</TabControl>
</Window>
With this in the form's code:
class Foo { public string Bar { get { return DateTime.Now.Ticks.ToString(); } } }
void Button_Click(object sender, RoutedEventArgs e)
{
if (lst.ItemsSource == null)
lst.ItemsSource = new ObservableCollection<Foo>();
for (int j = 0; j < 50; j++)
(lst.ItemsSource as IList<Foo>).Add(new Foo());
}
After clicking the button, scrolling the ListBox's content becomes less fluid, and there is a delay when switching back to Tab 1.
Any ideas on why this is happening, and on if there is anything to do about it?
Instead of hosting multiple WindowsFormsHost in a WPF ListBox's items, why cant you host one WinForm ListBox having above items in a single WindowsFormsHost? This would not only make the scrolling easy (due to entirely in WinForms UI context) but also make your WPF app memory efficient (due to single WindowsFormsHost).
If this is not what you are for, then try to loose the Virtualisation of your ListBox's Items Panel (which by default is a VirtualizedStackPanel).
Let me know if this helps?
Related
I have created a couple of UserControl views and now I want to show the corresponding view when a tab item is clicked. So one tab item gets one view. I would like to do this in MVVM but don't know how.
Please take a look at the following code and give me some advice on how to achieve that:
The MainView (with the TabControl only):
...
<TabControl Name="pnlFormButtons"
Margin="25"
Background="Black"
SelectedItem="{Binding SelTab}"
>
<TabItem Name="tabInventurartikel" Header="Inventurartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
>
</TabItem>
<TabItem Name="tabSonderartikel" Header="Sonderartikel hinzufügen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2">
</TabItem>
<TabItem Name="tabAnlegen" Header="Lieferschein anlegen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabDrucken" Header="Lieferschein drucken"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabHilfeseite" Header="Hilfeseite aufrufen"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
<TabItem Name="tabFehlerMelden" Header="Fehler bzw. Bug melden"
Background="BlanchedAlmond" Foreground="Black"
FontFamily="Verdana"
BorderBrush="Black"
BorderThickness="2"
IsEnabled="False">
</TabItem>
</TabControl>
...
The MainViewModel (only relevant code):
...
//Binding Property SelTab - It binds to the selected tab item
private string _selTab;
public string SelTab
{
get { return _selTab; }
set
{
_selTab = value;
OnPropertyChanged("SelTab"); //INotifyPropertyChanged
GetSelTab(); //check which tab item is selected and display the corresponding view
}
}
public void GetSelTab()
{
UserControl usc = null; //initialize user control object
switch(SelTab) //which tab item is selected?
{
case "tabInventurartikel": // = TabControl.SelectedItem
usc = new Inventurartikel(); //Initialize (Show) Inventurartikel.xaml
SelTab.Content = usc; //Here I don't know how to actually show the view in the tab item because SelectedItem.Content does not exist...
break;
case "tabSonderartikel":
usc = new neuerArtikel(); //same problem here...
break;
default:
break;
}
}
...
NOTE:
The views for the tab items are basically just user control forms that I want to show inside the tab item when the corresponding tab item is selected.
I shouldn't post them here because I want to keep the focus on the actual problem as simple and as clear as possible. Any help is highly appreciated!
The easiest solution would be to bind the tab control's item source to a list of view models. Then, if you add/remove view models, tabs are added/removed accordingly.
Main window xaml:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="CustomHeaderTemplate">
<Label Content="{Binding TabName}" />
</DataTemplate>
</Grid.Resources>
<TabControl x:Name="tbCtrl" ItemsSource="{Binding Items}" Loaded="tbCtrl_Loaded" SelectionChanged="tbCtrl_SelectionChanged" ItemTemplate="{StaticResource CustomHeaderTemplate}">
<TabControl.ContentTemplate>
<DataTemplate>
<uc:DeviceTab/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
The important thing is the binding of ItemSource.
Tab control view model:
class TabControlViewModel
{
public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>();
}
Tab control code behind Loaded event. Here you can add view models and the tab control sets up the tabs accordingly:
private void tbCtrl_Loaded(object sender, RoutedEventArgs e)
{
var tabControlViewModel = new TabControlViewModel();
tabControlViewModel.Items.Add(new ItemViewModel());
DataContext = tabControlViewModel;
tbCtrl.SelectedIndex = 0;
}
This only works if all tabs are the same. There's also a solution if you need different user controls for each tab. In that case, you need to specify a data template for the tab item's content. Basically you can tell it to load user controls based on the type of the view model. Unfortunately I don't know how to do that, but I've seen examples for it. I know it's not the exact answer you need, but I hope it helps!
I have a button. I want it to be a favorite toggle button inside a listbox. See code below:
<Page
x:Class="W.Pages.ExPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Workout_EF.Pages"
xmlns:converter="using:W.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:W.Model"
mc:Ignorable="d">
<Page.Resources>
<converter:FavoriteValueConverter x:Key="favoriteConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListBox Name="MyListbox"
SelectionMode="Single"
ItemsSource="{x:Bind exs}"
SelectionChanged="MyListbox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="data:Ex">
<StackPanel Orientation="Horizontal">
<Button Name="IsFavoriteToggle" Click="IsFavoriteToggle_Click">
<Button.Content>
<TextBlock
x:Name="isFavoriteTextBlock"
Text="{x:Bind IsFavorite, Converter={StaticResource favoriteConverter}}"
FontFamily="Segoe MDL2 Assets"/>
</Button.Content>
</Button>
<TextBlock
VerticalAlignment="Center"
FontSize="16"
Text="{Binding ExName}"
Margin="20,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Page>
My problem is when I hit this button it does not change the icon in it (from emtpy star to full star and vice versa) in real time.
If the listbox will be loaded again the correct icon is displayed.
The code behind is:
namespace W.Pages
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class ExPage : Page
{
ObservableCollection<Exs> exs = new ObservableCollection<Exs>();
public ExPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
List<Exs> tmpEx = e.Parameter as List<Exs>;
foreach (Exs item in tmpEx)
{
exs.Add(item);
}
}
private void IsFavoriteToggle_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
int index = MyListbox.Items.IndexOf(button.DataContext);
Ex ex = (Exs)MyListbox.Items[index];
DAL.SetToFavorite(ex, !ex.IsFavorite);
}
}
}
I noticed that there is some problem with the itemsource maybe. It needed to change its content after hitting the button. But I am not sure.
This is a common mistake that everyone make and that is ObservableCollection<T> is actually informing the binders about the changes in the collection not the objects in the collection.
You have a IsFavorite property in your Esx class that the button need to know about changes but for that, Esx needs to implement INotifyPropertyChanged
See this and if you need more help post the code for Esx class maybe we can help.
As Emaud said, you have to implement the INotifyPropertyChanged interface in your Esx class. And you also have to set set the Mode to OneWay in your binding because for x:Bind the default mode is OneTime so it does NOT listen to any changes.
Text="{x:Bind IsFavorite, Mode=OneWay, Converter={StaticResource favoriteConverter}}"
I have a WPF Custom Control that I use to display images.
There is a list view which is bound to an observable collection of database entities, which is subsequently converted into an image by virtue of a value converter.
When I drag the control onto a windows forms project (using a WPF Host control) it works perfectly when assigning the observable collection behind a list. I have tested this and it updates correctly and does everything i need it to.
HOWEVER
I would like to have three such controls displaying related images so I created a second control which simply grouped the three original controls into a stack panel.
I created a method for each that updates the images property.
public void ChangeSearchResults(List<ItemImage> items)
{
SearchResultsImageViewer.ItemImages = new ObservableCollection<ItemImage>(items);
}
However I simply cant get the images to show.
There seems to be a difference between viewing a control directly and viewing a control as a child control.
I am pretty sure it is not the element host in winforms as the control works well by itself.
Is there something I am not realising?
This is the Xaml for the list view
<!-- Sets the template for the data to be displayed -->
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Defines the actual image being displayed -->
<Image x:Name="ItemImageControl"
Width="100"
Height="200"
Margin="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="Hand"
Source="{Binding .,
Converter={StaticResource imageConverter}}" />
<TextBlock HorizontalAlignment="Center"
FontWeight="Bold"
Text="{Binding .,
Converter={StaticResource groupNameConverter}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT - this is the user control XAML
<UserControl x:Class="Project.CustomControls.ctrlImageCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:Project.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<CustomControls:ctrlImageViewer x:Name="ShortlistImageViewer" />
<CustomControls:ctrlImageViewer x:Name="SearchResultsImageViewer" />
<CustomControls:ctrlImageViewer x:Name="GroupImageViewer" />
</StackPanel>
</UserControl>
With using
...
Source={Binding ., Converter={StaticResource imageConverter}}" />
you bind to the current UserControl. When you use your ListView alone, it is bound to the UserControl containing the ListView and will work.
If you put your ListView UserControl into another UserControl its values get bound to its parent UserControl, not longer "own" UserControl.
Try this:
Go to your ListView UserControl xaml.cs and set the DataContext to itself.
//...
DataContext = this;
//...
I'm working on a Silverlight project, using MVVM, and I've run into a problem that only appears to occur under some fairly specific situations. I've tried to strip everything down as much as possible so only the important parts are left.
The Scenario:
A standard Silverlight ChildWindow
The ChildWindow has a Selector Control (e.g. a ComboBox or a ListBox).
The ChildWindow has a ContentPresenter
The Content of this ContentPresenter is bound to the SelectedValue of the above mentioned Selector Control (In reality it is bound to a ViewModel property, but for testing purposes this is not necessary).
The ContentPresenter uses a DataTemplateSelector to determine it's ContentTemplate
The Selector Control contains the string values "A" and "B" which correspond to DataTemplates "TemplateA" and "TemplateB".
"TemplateB" contains a TimePicker control.
The Problem:
After selecting "B" (therefore loading "TemplateB"), the next time you try to change the selected template, the host webpage will freeze. No exception is thrown and no information is given.
Notes:
Everything works fine outside of a ChildWindow.
Everything works fine if none of the templates contain a TimePicker control.
The TimePicker control appears to work fine if it is displayed without using a ContentPresenter/DataTemplateSelector.
Ive looked at TimePicker in ChildWindow causes an exception on the Silverlight Toolkit CodePlex page. That particular issue appears to be resolved and I've tried implementing the suggested workaround just to be sure, and it has no effect.
Code to reproduce the problem:
ChildWindow XAML:
<controls:ChildWindow
x:Class="TimePickerProblem.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:local="clr-namespace:TimePickerProblem"
Width="400"
Height="300"
Title="ChildWindow1">
<controls:ChildWindow.Resources>
<local:ViewModel
x:Key="vm" />
</controls:ChildWindow.Resources>
<Grid>
<Grid
x:Name="LayoutRoot"
Background="White"
DataContext="{StaticResource vm}">
<StackPanel
HorizontalAlignment="Center">
<ComboBox
x:Name="ComboBox"
Margin="20"
ItemsSource="{Binding Templates}" />
<ContentPresenter
Content="{Binding ElementName=ComboBox, Path=SelectedValue}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<local:TemplateSelector
Content="{Binding}">
<local:TemplateSelector.TemplateA>
<DataTemplate>
<TextBlock
Text="Hello from A" />
</DataTemplate>
</local:TemplateSelector.TemplateA>
<local:TemplateSelector.TemplateB>
<DataTemplate>
<toolkit:TimePicker />
</DataTemplate>
</local:TemplateSelector.TemplateB>
</local:TemplateSelector>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</StackPanel>
</Grid>
<Button
x:Name="CancelButton"
Content="Cancel"
Click="CancelButton_Click"
Width="75"
Height="23"
HorizontalAlignment="Right"
Margin="0,12,0,0"
Grid.Row="1" />
<Button
x:Name="OKButton"
Content="OK"
Click="OKButton_Click"
Width="75"
Height="23"
HorizontalAlignment="Right"
Margin="0,12,79,0"
Grid.Row="1" />
</Grid>
ViewModel:
public List<string> Templates { get { return new List<string>() { "a", "b" }; } }
DataTemplateSelector:
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate TemplateA { get; set; }
public DataTemplate TemplateB { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
string value = (string)item;
switch (value.ToLower())
{
case "a":
return TemplateA;
case "b":
return TemplateB;
default:
return base.SelectTemplate(item, container);
}
}
}
I've just tried in SL5, and couldn't reproduce your problem. If I've time, I will try SL4. Do you have any styles present that might cause a problem?
If you are describing a situation where overlay appears after dismissing ChildWindow, then it is known bug. Here's a workaround.
ChildWindow w = new MyChildWindow();
w.Closed += (s, eargs) => { Application.Current.RootVisual.SetValue(Control.IsEnabledProperty, true); };
w.Show();
I am writing an application in which I utilize a tab control which will start with one tab open but allows the user to open multiple other tabs.
Each tab that is openned should have a treeview inside which I fill using databinding when the user loads a file.
I am new to WPF but I feel as if there is a way in which I can create a template containing each of the elements the TabItems should contain. How can I do this using templates? Right now my WPF for the tab items is the following
<TabItem Header="Survey 1">
<TreeView Height="461" Name="treeView1" VerticalAlignment="Top"
Width="625" Margin="0,0,6,0" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" />
</TabItem>
I think you want something like this:
<Window x:Class="TestWpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="TabItemTemplate">
<TreeView Height="461" Name="treeView1" VerticalAlignment="Top"
Width="625" Margin="0,0,6,0" ItemTemplateSelector="{StaticResource TreeviewDataSelector}" />
</DataTemplate>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding ListThatPowersTheTabs}"
ItemTemplate="{StaticResource TabItemTemplate}">
</TabControl>
</Grid>
You basically create re-usable templates as static resources which you refer to by their key name.
Usually in this sort of situation I bind my TabControl.ItemsSource to a ObservableCollect<ViewModelBase> OpenTabs, so my ViewModel is in charge of adding/removing new tabs as needed.
Then if you want something in every Tab, then overwrite the TabControl.ItemTemplate and use the following line to specify where to display the currently selected TabItem
<ContentControl Content="{Binding }" />
If you don't need to setup something in every single tab, you don't need to overwrite the TabControl.ItemTemplate - it will default to displaying the currently selected ViewModelBase in the Tab.
And I use DataTemplates to specify which View to use
<DataTemplate TargetType="{x:Type local:TabAViewModel}">
<local:TabAView />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:TabBViewModel}">
<local:TabBView />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:TabCViewModel}">
<local:TabCView />
</DataTemplate>