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?