WPF TreeView Slow Loading - wpf

I have a tree view like this
<TreeView x:Name="tvFolders"
ItemsSource="{Binding TreeItems}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
BorderBrush="{StaticResource ColligoBorderLightBrush}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.IsVirtualizing="True"
Loaded="tvFolders_Loaded">
</TreeView>
The binding TreeItems is an ObservableCollection.
If this tree is not very large, this works great but if I have many folders/subfolders structure it can take 10 seconds or so until it loads.
How do I solve the issue so tree is built faster?

Lazy loading can be done as mentioned below. Since it not good practice to post any links. I am posting links as well as code content in the link.
I got it from here. http://www.wpf-tutorial.com/treeview-control/lazy-loading-treeview-items/
<Grid>
<TreeView Name="trvStructure" TreeViewItem.Expanded="TreeViewItem_Expanded" Margin="10" />
</Grid>
public partial class LazyLoadingSample : Window
{
public LazyLoadingSample()
{
InitializeComponent();
DriveInfo[] drives = DriveInfo.GetDrives();
foreach(DriveInfo driveInfo in drives)
trvStructure.Items.Add(CreateTreeItem(driveInfo));
}
public void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem item = e.Source as TreeViewItem;
if((item.Items.Count == 1) && (item.Items[0] is string))
{
item.Items.Clear();
DirectoryInfo expandedDir = null;
if(item.Tag is DriveInfo)
expandedDir = (item.Tag as DriveInfo).RootDirectory;
if(item.Tag is DirectoryInfo)
expandedDir = (item.Tag as DirectoryInfo);
try
{
foreach(DirectoryInfo subDir in expandedDir.GetDirectories())
item.Items.Add(CreateTreeItem(subDir));
}
catch { }
}
}
private TreeViewItem CreateTreeItem(object o)
{
TreeViewItem item = new TreeViewItem();
item.Header = o.ToString();
item.Tag = o;
item.Items.Add("Loading...");
return item;
}
}

Related

How to do an update my TreeView in wpf?

Good evening.
I started studying the technology - wpf in c#
Now I stopped at the section - work with treeview
I want to add a new node in my treeview in my task.
I have a form and button on this form:
...
<Button Name="btn1" Click="ShowForWriteAdrBookName" Content="TestBtn"/>
...
When you run the program on my form is loaded UserControl, which have treeview:
...
<TreeView Name="treeView1"></TreeView>
...
When I press the
...
<Grid>
<Label Name="Label1" Content="Имя адресной книги:" Margin="0,20,0,0"></Label>
<TextBox Name="TextBox1" Width="200" Height="30" Margin="90,-40,0,0"></TextBox>
<Button Content="Ок" Width="80" Height="30" Margin="0,50,0,0" Click="GetAdrBookName"></Button>
<Button Content="Отмена" Width="80" Height="30" Margin="200,50,0,0" Click="MyExit"></Button>
</Grid>
...
After entering text in the TextBox I click OK. Function is called:
...
public void GetAdrBookName(object sender, RoutedEventArgs e)
{
if (String.Compare(TextBox1.Text.ToString(), "") != 0)
{
adrName = TextBox1.Text.ToString().Trim();
this.Close();
CreateAdrBook(adrName);
}
else
{
...
}
}
...
In this function, I read information from textbox. And then call another function:
...
public void CreateAdrBook(string adrNameFromTextBox1)
{
mylist.Add(adrNameFromTextBox1);
int s = mylist.Count;
TreeView();
}
...
In this function, I add getting information from my textbox to my collection:
...
ObservableCollection<string> mylist = new ObservableCollection<string>()
{
"A",
"B",
"C"
};
...
After I added more information in my collection, I add this information to my TreeView in function:
...
void TreeView()
{
foreach (var drive in mylist)
{
TreeViewItem item = new TreeViewItem();
item.Tag = drive;
item.Header = drive.ToString();
item.Items.Add("*");
treeView1.Items.Add(item);
treeView1.Items.Refresh();
treeView1.UpdateLayout();
}
}
...
Question:
How to do an update my TreeView after adding in this TreeView a new element?
Thank you!

Referencing a databound ContextMenu inside a LongListSelector ItemTemplate - Windows Phone

I am writing a Silverlight for Windows Phone 7.5 app.
I want to reference the ContextMenu inside my LongListSelector because I want to set .IsOpen to false as soon as the ContextMenu Click event is called. My thought was that this should happen automatically but it does not.
One of my MenuItem's sets the visibility of a <Grid> from collapsed to visible which mimics a PopUp. Whilst the code executes fine and the Visibility does indeed change. The UI of the app does not show the Grid unless the ContextMenu closes.
My XAML of the LongListSelector which contains a ContextMenu called Menu that I wish to reference in the ContextMenuItem Click event.
<toolkit:LongListSelector x:Name="moviesLongList" Background="Transparent" IsFlatList="False" GroupHeaderTemplate="{StaticResource GroupHeaderTemplate}" GroupItemTemplate="{StaticResource GroupItemTemplate}" SelectionChanged="moviesLongList_SelectionChanged" GroupViewClosing="moviesLongList_GroupViewClosing" GroupViewOpened="moviesLongList_GroupViewOpened">
<toolkit:LongListSelector.GroupItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</toolkit:LongListSelector.GroupItemsPanel>
<toolkit:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Height="91" Margin="20,0,0,20" Orientation="Horizontal">
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu x:Name="Menu" Opened="ContextMenu_Opened" Loaded="Menu_Loaded" Unloaded="Menu_Unloaded">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="ContextMenuButton_Click" LostFocus="MenuItem_LostFocus" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<Border HorizontalAlignment="Left" Width="61" Height="91" Background="{Binding ID, Converter={StaticResource ThumbImageConvert}}" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" Width="395">
<TextBlock x:Name="titleTextBox" Text="{Binding Title, Converter={StaticResource TitleConvert}}" Margin="6,0,6,0" d:LayoutOverrides="Width" FontSize="{StaticResource PhoneFontSizeLarge}" VerticalAlignment="Top" HorizontalAlignment="Left"/>
<TextBlock x:Name="yearTextBox" Text="{Binding Year}" Margin="12,0,0,0" HorizontalAlignment="Left" FontSize="{StaticResource PhoneFontSizeMedium}" Foreground="{StaticResource PhoneSubtleBrush}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</toolkit:LongListSelector.ItemTemplate>
</toolkit:LongListSelector>
My code behind ContextMenuItem Click event
private void ContextMenuButton_Click(object sender, RoutedEventArgs e)
{
//
// This is where I want to set Menu.IsOpen = false to close the ContextMenu.
//
if ((sender as MenuItem).Header.ToString() == "lend movie")
{
DisableAppBarIcons();
LendPopUpOverlay.Visibility = System.Windows.Visibility.Visible;
}
if ((sender as MenuItem).Header.ToString() == "return to collection")
{
... Do stuff
}
if ((sender as MenuItem).Header.ToString() == "add to boxset")
{
... Do stuff
}
if ((sender as MenuItem).Header.ToString() == "delete")
{
... Do stuff
}
}
I set the ItemSource of the ContextMenu in the ContextMenu_Opened event. The fields are both of type List<String>.
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
LentMovieObj = (sender as ContextMenu).DataContext as Movies;
if (LentMovieObj.IsLent)
{
(sender as ContextMenu).ItemsSource = menuItemsReturn;
}
else
{
(sender as ContextMenu).ItemsSource = menuItemsLendOut;
}
}
Not sure why the ContextMenu is not closing, but here are two solutions. The first is to get the parent of the MenuItem.
private T GetParentOfType<T>(DependencyObject obj) where T : class
{
if (obj == null) return null;
var parent = VisualTreeHelper.GetParent(obj);
while (parent != null)
{
if (parent is T) return parent as T;
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
Then get the Menu from your click handler
var menu = GetParentOfType<ContextMenu>(sender as MenuItem);
menu.IsOpen = false;
The second solution is to bind IsOpen to a backing viewmodel
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu x:Name="Menu" Opened="ContextMenu_Opened" Loaded="Menu_Loaded" Unloaded="Menu_Unloaded" IsOpen="{Binding IsOpen}" ItemsSource="{Binding Items}">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="ContextMenuButton_Click" LostFocus="MenuItem_LostFocus" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
Change your open event:
private void ContextMenu_Opened(object sender, RoutedEventArgs e)
{
LentMovieObj = (sender as ContextMenu).DataContext as Movies;
if (LentMovieObj.IsLent)
{
(sender as ContextMenu).DataContext = new ContextMenuViewModel(menuItemsReturn);
}
else
{
(sender as ContextMenu).DataContext = ContextMenuViewModel(menuItemsLendOut);
}
}
Then a viewmodel
public class ContextMenuViewModel : INotifyPropertyChanged
{
private bool _isOpen = true;
public ContextMenuViewModel(IEnumerable<string> items)
{
Items = items;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsOpen
{
get { return _isOpen; }
set { _isOpen = value; OnPropertyChanged("IsOpen"); }
}
public IEnumerable<String> Items { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then set the IsOpen property of your ViewModel to false;

Delete items from ListBox in WPF?

I am trying to delete items from listbox which is data bound.
Here is the screenshot how listbox look like.
This is the code which adds items in lists.
public class Task
{
public string Taskname { get; set; }
public Task(string taskname)
{
this.Taskname = taskname;
}
}
public void GetTask()
{
taskList = new List<Task>
{
new Task("Task1"),
new Task("Task2"),
new Task("Task3"),
new Task("Task4")
};
lstBxTask.ItemsSource = taskList;
}
This is the Xaml code,
<ListBox x:Name="lstBxTask" Style="{StaticResource ListBoxItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Taskname}" Style="{StaticResource TextInListBox}"/>
<Button Name="btnDelete" Style="{StaticResource DeleteButton}" Click="btnDelete_Click">
</Button>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Whenever item in a listbox is selected, delete (x) button is displayed. When clicked it should delete that item from the listbox. Can anyone tell me how can I do this?
ok this is what i did. Observablecollection worked like charm.
private ObservableCollection<Task> taskList;
public void GetTask()
{
taskList = new ObservableCollection<Task>
{
new Task("Task1"),
new Task("Task2"),
new Task("Task3"),
new Task("Task4")
};
lstBxTask.ItemsSource = taskList;
}
private void btnDelete_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
var task = button.DataContext as Task;
((ObservableCollection<Task>) lstBxTask.ItemsSource).Remove(task);
}
else
{
return;
}
}
Try using an ObservableCollection<T> instead of a simple List<T>.
The ObservableCollection<T> will notify the WPF-binding-system whenever its content has changed. Therefore, you will only have to remove the item from the list and the UI will be updated.

WPF / Silverlight Binding when setting DataTemplate programmatically

I have my little designer tool (my program).
On the left side I have TreeView and on the right site I have Accordion.
When I select a node I want to dynamically build Accordion Items based on Properties from DataContext of selected node.
Selecting nodes works fine, and when I use this sample code for testing it works also.
XAML code:
<layoutToolkit:Accordion x:Name="accPanel"
SelectionMode="ZeroOrMore"
SelectionSequence="Simultaneous">
<layoutToolkit:AccordionItem Header="Controller Info">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBlock Text="Content:" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</layoutToolkit:AccordionItem>
</layoutToolkit:Accordion>
C# code:
private void treeSceneNode_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue != e.OldValue)
{
if (e.NewValue is SceneNode)
{
accPanel.DataContext = e.NewValue; //e.NewValue is a class that contains Name property
}
}
}
But the problem occurs when I'm trying to achive this using DateTemplate and dynamically build AccordingItem, the Binding is not working:
<layoutToolkit:Accordion x:Name="accPanel"
SelectionMode="ZeroOrMore"
SelectionSequence="Simultaneous" />
and DataTemplate in my ResourceDictionary
<DataTemplate x:Key="dtSceneNodeContent">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBlock Text="Content:" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
and C# code:
private void treeSceneNode_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue != e.OldValue)
{
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new Uri("/SilverGL.GUI;component/SilverGLDesignerResourceDictionary.xaml", UriKind.RelativeOrAbsolute);
if (e.NewValue is SceneNode)
{
accPanel.DataContext = e.NewValue;
AccordionItem accController = new AccordionItem();
accController.Header = "Controller Info";
accController.ContentTemplate = rd["dtSceneNodeContent"] as DataTemplate;
accPanel.Items.Add(accController);
}
else
{
// Other type of node
}
}
}
Are you missing this?
accController.Content = e.NewValue;
Also, I don't think you need to use DataContext="{Binding}"; the DataContext will inherit anyway.

Silverlight - Get the ItemsControl of a DataTemplate

I have a Silverlight application that is using a DataGrid. Inside of that DataGrid I have a DataTemplate that is defined like the following:
<Grid x:Name="myGrid" Tag="{Binding}" Loaded="myGrid_Loaded">
<ItemsControl ItemsSource="{Binding MyItems}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Width="138">
<TextBlock Text="{Binding Type}" />
<TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When a user enters text into the TextBox, I have an event (myTextBox_TextChanged) that must be fired at this point. When that event gets fired, I would like to get the ItemsControl element that is the container for this TextBox. How do I get that ItemsControl from my event handler?
Please note: Because the ItemsControl is in the DataTemplate of DataGrid, I don't believe I can just add an x:Name and reference it from my code-behind. Or is there a way to do that?
Thank you!
Using a combination of ItemsControl.ItemsControlFromItemContainer and VisualTreeHelper.GetParent you should be able to find your ItemsControl
var txt = sender as TextBox;
var panel1 = VisualTreeHelper.GetParent(txt);
var panel2 = VisualTreeHelper.GetParent(panel1);
var contentPresenter = VisualTreeHelper.GetParent(panel2);
var ic = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
You may also want search the web for VisualTreeHelper Recursive functions to make some of this easier.
I like to have this little extension method in a static class somewhere in my app:-
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject root)
{
DependencyObject current = VisualTreeHelper.GetParent(root);
while (current != null)
{
yield return current;
current = VisualTreeHelper.GetParent(current);
}
}
With that you should be able to do something like this:-
ItemsControl control = ((DependencyObject)sender).Ancestors()
.TypeOf<ItemsControl>().FirstOrDefault();
Not sure if this applies but this creates a "toggling button bar" using the same principles.
private void UIClassButton_Click(object sender, RoutedEventArgs e){
Button SenderButton = (Button)sender;
ItemsControl SendersItemControl = ItemsControl.ItemsControlFromItemContainer(VisualTreeHelper.GetParent(SenderButton));
IEnumerable<DependencyObject> DependencyObjectCollection = SendersItemControl.GetContainers();
foreach (ContentPresenter item in DependencyObjectCollection) {
ContentPresenter UIClassPresenter = (ContentPresenter)item;
Button UIClassButton = (Button)UIClassPresenter.GetVisualChildren().First();
if (UIClassButton != SenderButton) {
VisualStateManager.GoToState(UIClassButton, "Normal", true);
}
else {
VisualStateManager.GoToState(UIClassButton, "Pressed", true);
}
}
}
Here's an example of capturing a container that houses your ItemsControl's item:
CheckBox checkbox = sender as CheckBox;
foreach (var item in MembersItemsControl.Items)
{
var container = MembersItemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
UserInformation user = container.DataContext as UserInformation;
bool isMale = true;
if (user.sex == isMale && checkbox.IsChecked.Value == true)
{
container.Visibility = System.Windows.Visibility.Visible;
}
}
I hope that helps.

Resources