How to do an update my TreeView in wpf? - 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!

Related

how can i add item to Combobox in wpf at runtime

how can I bind data to Combobox at runtime? I use template field in Combobox and i try to update Combobox item source in code-behind. but not update xamarin my Combobox in form. and in combobox template field, i want to delete combobox item with a button that event name cbxDeleteStudent_Click. but i can't find comboxitem in code behind.
Please help me.
MyCodes:
<ComboBox x:Name="cbxStudents" Width="150" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<DockPanel Width="150">
<Label Content="{Binding StudentId}" x:Name="cbxStudentId"></Label>
<Label Content="{Binding StudentName}"></Label>
<Button Content="Sil" x:Name="cbxDeleteStudent" HorizontalAlignment="Right" Width="35"
CommandParameter="{Binding StudentId}" Click="cbxDeleteStudent_Click"></Button>
</DockPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code Behind
private void btnAddNewStudent_Click(object sender, RoutedEventArgs e)
{
using (EmployeeDbContext db = new EmployeeDbContext())
{
Student newStudent = new Student()
{
StudentName = txtStudent.Text
};
db.Students.Add(newStudent);
if (db.SaveChanges() > 0)
{
MessageBox.Show(string.Format("{0} öğrencisi başarı ile eklenmiştir.", txtStudent.Text), "Bilgi", MessageBoxButton.OK);
txtStudent.Text = string.Empty;
(cbxStudents.ItemsSource as List<Student>).Add(newStudent);
}
}
}
for delete combobox item
private void cbxDeleteStudent_Click(object sender, RoutedEventArgs e)
{
using (EmployeeDbContext db = new EmployeeDbContext())
{
Student selectedStudent = db.Students.Find(int.Parse((sender as Button).CommandParameter.ToString()));
db.Students.Remove(selectedStudent);
db.SaveChanges();
}
((sender as Button).Parent as DockPanel).Children.Clear();
}
It looks like the the ItemSource used to bind to the ComboBox, is a List<Student>.
Use ObservableCollection(Of T) instead of List<T>, ObservableCollection provides notification when items get added, removed, or when the whole list is refreshed and the ComboBox items are updated, while List<T> doesn't.
Then you just need to add/remove the item from the ObservableCollection, without having to touch the ComboxBox's Items property.
To Add
(cbxStudents.ItemsSource as ObservableCollection<Student>).Add(newStudent);
To Remove
ObservableCollection<Student> students = cbxStudents.ItemsSource as ObservableCollection<Student>;
int studentId = int.Parse((sender as Button).CommandParameter.ToString());
Student selectedStudent = students.SingleOrDefault(s => s.StudentId == studentId);
students.Remove(selectedStudent);

WPF TreeView Slow Loading

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;
}
}

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.

Accordion Item not in correct Visual State

I need to use an Accordion to display some totals on a LOB application we are building.
If I place the Accordion in XAML all works fine and the state of the icon (>) is correct and pointing to the right. On Mouse entering the AccordionItem we do not have a visual state change.
If I dynamically add AccordionItems on a Button Click (to simulate async data call returning) the state of the icon is not the same and on MouseEnter it "corrects" itself by executing a visual state change. *You may need to click "Add 3 Accordion Items" twice.
If I dynamically add an Accordion on a Button click with AccordionItems it works fine. Below is my sample Application.
So what do I need to do to get the Accordion to add AcordionItems at runtime and be in the correct state as per when using XAML?
XAML
<Grid x:Name="LayoutRoot" Background="Black" >
<StackPanel x:Name="TheStackPanel">
<Button Content="Create Accordion" Click="CreateAccordionItems"></Button>
<Button Content="Add 3 Accordion Items" Click="AddAccordionItems"></Button>
<Grid Background="Pink">
<layoutToolkit:Accordion SelectionMode="ZeroOrMore" x:Name="TestAccordion" Margin="10,10,10,10" HorizontalAlignment="Stretch" >
<layoutToolkit:AccordionItem Content="Content - 1" Header="Header - 1">
</layoutToolkit:AccordionItem>
<layoutToolkit:AccordionItem Content="Content - 2" Header="Header - 2">
</layoutToolkit:AccordionItem>
<layoutToolkit:AccordionItem Content="Content - 3" Header="Header - 3">
</layoutToolkit:AccordionItem>
</layoutToolkit:Accordion>
</Grid>
</StackPanel>
public partial class MainPage : UserControl
{
private int count = 0;
public MainPage()
{
// Required to initialize variables
InitializeComponent();
//TestAccordion.ExpandDirection = ExpandDirection.Down;
}
private void AddAccordionItems( object sender, RoutedEventArgs e )
{
AddToAccordion( 3, TestAccordion );
}
private void AddToAccordion( int size, Accordion _Accordion )
{
for( int i = 0; i < size; i++ )
{
AccordionItem accordionItem = new AccordionItem( );
accordionItem.Header = "Item " + count.ToString( );
count++;
_Accordion.Items.Add( accordionItem );
Grid aGrid = new Grid( );
TextBlock tb = new TextBlock( );
tb.Text = accordionItem.Header as string;
aGrid.Children.Add( tb );
accordionItem.Content = aGrid;
//accordionItem.IsEnabled = true;
accordionItem.IsSelected = true;
}
}
private void CreateAccordionItems( object sender, RoutedEventArgs e )
{
Accordion accordion = new Accordion( );
accordion.HorizontalContentAlignment = HorizontalAlignment.Stretch;
TheStackPanel.Children.Add( accordion );
AddToAccordion( 10, accordion );
}
}
If you take a look at the source code for the Accordian control you'll see that it uses the InteractionHelper.UpdateVisualState to set its correct state after events.
public void UpdateVisualStateBase(bool useTransitions)
{
if (!this.Control.IsEnabled)
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "Disabled", "Normal" });
}
else if (this.IsReadOnly)
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "ReadOnly", "Normal" });
}
else if (this.IsPressed)
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "Pressed", "MouseOver", "Normal" });
}
else if (this.IsMouseOver)
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "MouseOver", "Normal" });
}
else
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "Normal" });
}
if (this.IsFocused)
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "Focused", "Unfocused" });
}
else
{
VisualStates.GoToState(this.Control, useTransitions, new string[] { "Unfocused" });
}
}
Since the method is marked internal on the Accordian control and the InteractionHelper is a private variable, your best bet is to figure out which of the states you're adding the control in and then tell the control to go to that state (without a transition) before addig it to the visual tree. This is why the MouseOver is "fixing" it.
Call
TestAccordion.UpdateLayout();
after adding the items... may be
Can you bind the accordian Items to an ObservableCollection?

TextBox, Button and ListBox in a ListBox

I have a listbox with a bunch of contols in each list item.
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<ListBox x:Name="taskList" ItemsSource="{Binding Tasks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox x:Name="textBoxTask" />
<Button
x:Name="ButtonAddNewTask"
Content="Test"
CommandParameter="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
Click="ButtonAddNewTask_Click"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When I click on the button in the listbox i want to add a new item to the listbox within the listbox. I've come this far. So my question is how do I get hold of the textbox and how do I update the listbox?
Here is my click event
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
proj.Tasks.Add(new Task("Added Task"));
}
Thanx
The easiest solution would likely be to have one object represent each item in the outer ListBox. It would then have properties that would represent each control in the item - the text in the TextBox, and the items in the ListBox (a list of Tasks, I think, based on your Click handler).
In your Click handler, you can get the Button's DataContext (which should be an item in the collection of the outer list), and add a new Task to that object's list of tasks. Since the inner ListBox is bound to that list, it should be updated with the new item (assuming that it sends events when items are added, such as with ObservableCollection).
Update: Based on your comments, the following should work.
Your Project class should have two properties:
class Project
{
public string Name { get; set; }
private ObservableCollection<Task> tasks =
new ObservableCollection<Task>();
public IList<Task> Tasks
{
get { return this.tasks; }
}
}
The Task class just has one property - the name of the task.
The ProjectView class is a wrapper around the Project class (I got this idea from #timothymcgrath's answer). It keeps track of the name of a new task, and the current Project:
class ProjectView : INotifyPropertyChanged
{
public Project Project { get; set; }
private string newTaskName = string.Empty;
public string NewTaskName
{
get { return this.newTaskName; }
set
{
this.newTaskName = value;
this.OnPropertyChanged("NewTaskName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler eh = this.PropertyChanged;
if(null != eh)
{
eh(this, new PropertyChangedEventArgs(propName));
}
}
}
You'll need a new class that will be used as the DataContext. Something like this:
class Model
{
private ObservableCollection<ProjectView> projects =
new ObservableCollection<ProjectView>();
public IList<ProjectView> Projects
{
get { return this.projects; }
}
}
In the code behind, set the DataContext of the object to an instance of the above class:
public class Window1
{
public Window1()
{
this.InitializeComponent();
this.DataContext = this.model;
}
private Model model = new Model();
}
In the XAML, the bindings should be modified to bind to the above properties:
<ListBox x:Name="projectList" IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Projects}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Project.Name}" />
<ListBox x:Name="taskList"
ItemsSource="{Binding Project.Tasks}"
DisplayMemberPath="Name" />
<TextBox x:Name="textBoxTask"
Text="{Binding Path=NewTaskName, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="ButtonAddNewTask" Content="Test"
Click="ButtonAddNewTask_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Finally, in the click handler for the button, create the task. The DataContext of the Button will be the ProjectView for that item.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)sender;
ProjectView curProject = btn.DataContext as Project;
if(null != curProject)
{
curProject.Project.Tasks.Add(new Task()
{
Name = curProject.NewTaskName
});
}
}
Since all of the controls get their values via binding, you don't need to access the control itself to get the data - just use the data structures that are supplying the controls already.
It would probably be better to move the code that creates the Task into another class (possibly Project), but I just left it in the event handler for ease of typing on my part.
Update 2: Modified the above code to move the NewTaskName property into a separate class that wraps an instance of Project for use with the UI. Does this work better for you?
I'm assuming your Project ListBox is populated with an Collection of Project objects. I would add an AddNewTask ICommand to the Project class and expose it through a property. Then bind the Add New Task button to the new AddNewTask ICommand. For the CommandParameter, put the TaskName in and it will be passed into the command.
Try reading up on some MVVM (Model View ViewModel) for some examples of how this works. It is very clean and works great.
This solution worked for the task at hand so to speak.
private void ButtonAddNewTask_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
DependencyObject obj = LogicalTreeHelper.GetParent(button);
StackPanel item = obj as StackPanel;
TextBox textBox = item.FindName("textBoxTask") as TextBox;
ListBox listBox = item.FindName("taskList") as ListBox;
Project proj = button.DataContext as Project;
if(proj.Tasks == null)
proj.Tasks = new List<Task>();
listBox.ItemsSource = proj.Tasks;
listBox.Items.Refresh();
}

Resources