I have this Custom Control
XAML:
<UserControl x:Class="WpfApplication1.UC"
...
x:Name="uc">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Horizontal">
<TextBox Text="{Binding Test, ElementName=uc}" Width="50" HorizontalAlignment="Left"/>
</StackPanel>
</UserControl>
C#
public partial class UC : UserControl
{
public static readonly DependencyProperty TestProperty;
public string Test
{
get
{
return (string)GetValue(TestProperty);
}
set
{
SetValue(TestProperty, value);
}
}
static UC()
{
TestProperty = DependencyProperty.Register("Test",typeof(string),
typeof(UC), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
public UC()
{
InitializeComponent();
}
}
And this is how i used that custom control:
<DockPanel>
<ItemsControl ItemsSource="{Binding Path=DataList}"
DockPanel.Dock="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" CommandParameter="{Binding}" Click="Button_Click"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
</DockPanel>
--
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
private ObservableCollection<string> _dataList;
public ObservableCollection<string> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
_selectedString = value;
OnPropertyChanged("SelectedString");
}
}
public MainWindow()
{
InitializeComponent();
this.DataList = new ObservableCollection<string>();
this.DataList.Add("1111");
this.DataList.Add("2222");
this.DataList.Add("3333");
this.DataList.Add("4444");
this.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.SelectedString = (sender as Button).CommandParameter.ToString();
}
}
If I do not change text of UC, everything is ok. When I click each button in the left panel, button's content is displayed on UC.
But when I change text of UC (ex: to 9999), Test property lost binding. When I click each button in the left panel, text of UC is the same that was changed (9999). In debug I see that SelectedString is changed by each button click but UC's text is not.
I can 'fix' this problem by using this <TextBox Text="{Binding Test, ElementName=uc, Mode=OneWay}" Width="50" HorizontalAlignment="Left"/> in the UC.
But I just want to understand the problem, can someone help me to explain it please.
Setting the value of the target of a OneWay binding clears the binding. The binding <TextBox Text="{Binding Test, ElementName=uc}" is two way, and when the text changes it updates the Test property as well. But the Test property is the Target of a OneWay binding, and that binding is cleared.
Your 'fix' works because as a OneWay binding, it never updates Test and the binding is never cleared. Depending on what you want, you could also change the UC binding to <local:UC Test="{Binding SelectedString, Mode=TwoWay}"/> Two Way bindings are not cleared when the source or target is updated through another method.
The issue is with below line
<local:UC Test="{Binding SelectedString, Mode=OneWay}"/>
The mode is set as oneway for SelectString binding so text will be updated when the value from code base changes. To change either the source property or the target property to automatically update the binding source as TwoWay.
<local:UC Test="{Binding SelectedString, Mode=TwoWay}"/>
Related
I'm trying to create a UserControl in my WPF project which I want should have a DependencyProperty that I can bind to in the parent. The project is written as MVVM and I'm using Caliburn micro.
I really want to write clean and maintainable code using MVVM, so I want my UserControls to utilize viewmodels as much as possible and code behind as little as possible.
The problem is that I'm unsuccessful in getting the binding between the parent and the UserControl viewmodel to work correctly.
MyUserControl:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
// If no Datacontext is set, binding between parent property and textbox text works - one way only (set from parent)!.
// -
// If Datacontext is set to this, bindings with properties in MyUserControlView code behind works.
//DataContext = this;
// If Datacontext is set to MyUserControlViewModel, binding between MyUserControlViewModel and MyUserControlView works, but not with parent.
DataContext = new MyUserControlViewModel();
}
public string ProjectNumber
{
get { return (string)GetValue(MyUserControlValueProperty); }
set { SetValue(MyUserControlValueProperty, value); }
}
public static readonly DependencyProperty MyUserControlValueProperty =
DependencyProperty.Register("ProjectNumber", typeof(string), typeof(MyUserControlView), new PropertyMetadata(null, new PropertyChangedCallback(OnProjectNumberUpdate)));
private static void OnProjectNumberUpdate(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var view = d as MyUserControlView;
view.ProjectNumberText.Text = e.NewValue as string;
}
}
MyUserControl code behind:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding ProjectNumber}" />
</StackPanel>
<TextBox Name="ProjectNumberText" Text="{Binding ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</StackPanel>
MyUserControl ViewModel:
public class MyUserControlViewModel : Screen
{
private string _projectNumber;
public string ProjectNumber
{
get { return _projectNumber; }
set
{
_projectNumber = value;
NotifyOfPropertyChange(() => ProjectNumber);
}
}
}
Parent view:
<StackPanel>
<local:MyUserControlView ProjectNumber="{Binding ParentProjectNumber}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="In parent: "/>
<TextBlock Text="{Binding ParentProjectNumber}" />
</StackPanel>
</StackPanel>
Parent ViewModel:
public class ShellViewModel : Screen
{
public ShellViewModel()
{
ParentProjectNumber = "Hello from parent!";
}
private string _parentProjectNumber;
public string ParentProjectNumber
{
get { return _parentProjectNumber; }
set
{
_parentProjectNumber = value;
NotifyOfPropertyChange(() => ParentProjectNumber);
}
}
}
I know I'm probably way off here, but I have no idea what to do to get the bindings to work correctly.
Is there a better way to bind between a DependencyProperty and a viewmodel? Can I put the DP in the viewmodel somehow?
Here is the entire project solution: https://github.com/ottosson/DependencyPropertyTest
don't change UserControl.DataContext from inside UserControl. it can and will create issues later.
use proper name for DP (ProjectNumberProperty and corresponding ProjectNumber) and add BindsTwoWayByDefault to metadata:
public partial class MyUserControlView : UserControl
{
public MyUserControlView()
{
InitializeComponent();
}
public string ProjectNumber
{
get { return (string)GetValue(ProjectNumberProperty); }
set { SetValue(ProjectNumberProperty, value); }
}
public static readonly DependencyProperty ProjectNumberProperty = DependencyProperty.Register
(
"ProjectNumber",
typeof(string),
typeof(MyUserControlView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
}
fix bindings in xaml:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="In MyUserControl: " />
<TextBlock Text="{Binding Path=ProjectNumber, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
<TextBox Text="{Binding Path=ProjectNumber, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</StackPanel>
that should do it.
btw, "clean and maintainable code using MVVM" and "want my UserControls to utilize viewmodels as much as possible" sort of contradict each other.
also nothing wrong with code-behind in UserControls as long as that code handles only view functionality. for example: DataGrid source code contains 8000+ LoC
I have an ItemsControl that should display the values of some properties of an object.
The ItemsSource of the ItemsControl is an object with two properties: Instance and PropertyName.
What I am trying to do is displaying all the property values of the Instance object, but I do not find a way to set the Path of the binding to the PropertyName value:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Source=??{Binding Path=Instance}??, Path=??PropertyName??, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the question marks are the points where I don't know how to create the binding.
I initially tried with a MultiValueConverter:
<TextBlock Grid.Column="1" Text="{Binding}">
<TextBlock.DataContext>
<MultiBinding Converter="{StaticResource getPropertyValue}">
<Binding Path="Instance" Mode="OneWay"/>
<Binding Path="PropertyName" Mode="OneWay"/>
</MultiBinding>
</TextBlock.DataContext>
</TextBlock>
The MultiValueConverter uses Reflection to look through the Instance and returns the value of the property.
But if the property value changes, this change is not notified and the displayed value remains unchanged.
I am looking for a way to do it with XAML only, if possible, if not I will have to write a wrapper class to for the items of the ItemsSource collection, and I know how to do it, but, since it will be a recurring task in my project, it will be quite expensive.
Edit:
For those who asked, InstanceProperties is a property on the ViewModel which exposes a collection of objects like this:
public class InstanceProperty : INotifyPropertyChanged
{
//[.... INotifyPropertyChanged implementation ....]
public INotifyPropertyChanged Instance { get; set; }
public string PropertyName { get; set; }
}
Obviously the two properties notify theirs value is changing through INotifyPropertyChanged, I don't include the OnPropertyChanged event handling for simplicity.
The collection is populated with a limited set of properties which I must present to the user, and I can't use a PropertyGrid because I need to filter the properties that I have to show, and these properties must be presented in a graphically richer way.
Thanks
Ok, thanks to #GazTheDestroyer comment:
#GazTheDestroyer wrote: I cannot think of any way to dynamically iterate and bind to an arbitrary object's properties in XAML only. You need to write a VM or behaviour to do this so you can watch for change notifications, but do it in a generic way using reflection you can just reuse it throughout your project
I found a solution: editing the ViewModel class InstanceProperty like this
added a PropertyValue property
listen to PropertyChanged event on Instance and when the PropertyName value changed is fired, raise PropertyChanged on PropertyValue
When Instance or PropertyName changes, save a reference to Reflection's PropertyInfo that will be used by PropertyValue to read the value
here is the new, complete, ViewModel class:
public class InstanceProperty : INotifyPropertyChanged
{
#region Properties and events
public event PropertyChangedEventHandler PropertyChanged;
private INotifyPropertyChanged FInstance = null;
public INotifyPropertyChanged Instance
{
get { return this.FInstance; }
set
{
if (this.FInstance != null) this.FInstance.PropertyChanged -= Instance_PropertyChanged;
this.FInstance = value;
if (this.FInstance != null) this.FInstance.PropertyChanged += Instance_PropertyChanged;
this.CheckProperty();
}
}
private string FPropertyName = null;
public string PropertyName
{
get { return this.FPropertyName; }
set
{
this.FPropertyName = value;
this.CheckProperty();
}
}
private System.Reflection.PropertyInfo Property = null;
public object PropertyValue
{
get { return this.Property?.GetValue(this.Instance, null); }
}
#endregion
#region Private methods
private void CheckProperty()
{
if (this.Instance == null || string.IsNullOrEmpty(this.PropertyName))
{
this.Property = null;
}
else
{
this.Property = this.Instance.GetType().GetProperty(this.PropertyName);
}
this.RaisePropertyChanged(nameof(PropertyValue));
}
private void Instance_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.PropertyName)
{
this.RaisePropertyChanged(nameof(PropertyValue));
}
}
private void RaisePropertyChanged(string propertyname)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion
}
and here is the XAML:
<ItemsControl ItemsSource={Binding Path=InstanceProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PropertyName, Mode=OneWay}"/>
<TextBlock Text=": "/>
<TextBlock Text="{Binding Path=PropertyValue, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am a beginner in programming, especially WPF. I have an application in WPF. I have changed connection to .sdf database from Entity Framework to SqlCeCommand. Unfortunatelly, before this I had the following code for binding a ComboBox.
<DockPanel Grid.Row="4">
<Button x:Name="LoadButton" Height="20" ToolTip="Choose setting name to load" Width="75" Padding="2,2,2,2" Margin="2,0,2,0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="Load Settings" Command="{Binding LoadSettingsCommand}"/>
<ComboBox x:Name="LoadSettingsComboBox" ToolTip="Choose setting name to load" ItemsSource="{Binding Mode=OneWay, Path=Settings/}" SelectedValue="{Binding LoadSettingName, Mode=OneWayToSource}" SelectedValuePath="Name" Grid.Column="1" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Center" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
and:
List<Setting> _settings;
Settings = new CollectionView(_settings);
And it worked. After changing connection to DataBase there is no error, but ComboBox doesn't show any data. Before Setting class was generated by entity framework. Now, I made my own class Setting. What this class should implement? Can you help me?
Vote this answer if you find it helpful.
As per my understanding if you are using Setting as data object you need to store it into ObservableCollection<>. Use like this :
private ObservableCollection<Settings> _settingList = new ObservableCollection<Settings>();
public ObservableCollection<Settings> SettingList
{
get
{
return this._settingList;
}
set
{
if(value==null)
return;
this._settingList = value;
//OnPropertyChanged(()=>this.SettingList); //It is not required as ObservableCollection<> itself notifies on collection changed.
}
}
If you are implementing your own Setting class then you should implement INotifyPropertyChanged interface to bind properties with combobox item. Below is the code for your reference:
public class SettingsModel : INotifyPropertyChanged
{
#region INotifyPropertyChanded event Implementation
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null == handler)
return;
if (null != propertyExpression)
{
var e = new PropertyChangedEventArgs(((MemberExpression)propertyExpression.Body).Member.Name);
handler(this, e);
}
}
public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != PropertyChanged)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Above implementation gives you a method OnPropertyChanged(string propertyName). You need to call this method on "set" section of your each property in your class. Whenever property value gets changed it will notify to the DependencyProperty of control.
Hope this will help you.
I have one main outstanding issue, I know now how to databind to lists and individual items, but there is one more problem I have with this, I need to bind a listbox to some properties that are in a Class.
For Example I have two Properties that are bound in some XAML in a class called Display:
Public Property ShowEventStart() As Visibility
Public Property ShowEventEnd() As Visibility
I can use these in my XAML but I want them to be in a ListBox/Drop-down List, how can I have my properties show in this list and be able to change their values, does it have to be a List to be in a List?
I just want to be able to modify these properties from a Drop-down list to toggle the Visibility of the ShowEventStart and ShowEventEnd Property Values using the Checkboxes in the Drop-down List.
Plus this must be a Silverlight 3.0 solution, I cannot figure out how to have something that can be bound in the XAML which is not a list and then bound it as a list to modify these items!
I just need a list of Checkboxes which alter the values of the Class Properties such as ShowEventStart and ShowEventEnd, there are other properties but this will be a start.
You can create a PropertyWrapper class and in you window code behind expose a property that returns a List<PropertyWrapper> and bind your ListBox.ItemsSource to it.
public class PropertyWrapper
{
private readonly object target;
private readonly PropertyInfo property;
public PropertyWrapper(object target, PropertyInfo property)
{
this.target = target;
this.property = property;
}
public bool Value
{
get
{
return (bool) property.GetValue(target, null);
}
set
{
property.SetValue(target, value, null);
}
}
public PropertyInfo Property
{
get
{
return this.property;
}
}
}
your window code behind:
public partial class Window1 : Window, INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
properties = new List<PropertyWrapper>
{
new PropertyWrapper(this, typeof(Window1).GetProperty("A")),
new PropertyWrapper(this, typeof(Window1).GetProperty("B")),
};
this.DataContext = this;
}
private List<PropertyWrapper> properties;
public List<PropertyWrapper> Properties
{
get { return properties; }
}
private bool a;
private bool b = true;
public bool A
{
get
{
return a;
}
set
{
if (value != a)
{
a = value;
NotifyPropertyChange("A");
}
}
}
public bool B
{
get
{
return b;
}
set
{
if (value != b)
{
b = value;
NotifyPropertyChange("B");
}
}
}
protected void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(
this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
your window markup:
<ListBox ItemsSource="{Binding Path=Properties}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Property.Name}"/>
<CheckBox IsChecked="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Hope this helps
I tried to mock up something similar. See how this works for you and let me know if I misunderstood the question.
From MainPage.xaml:
<StackPanel Orientation="Horizontal">
<ListBox SelectedItem="{Binding ShowControls, Mode=TwoWay}" x:Name="VisibilityList"/>
<Button Content="Test" Visibility="{Binding ShowControls}"/>
<CheckBox Content="Test 2" Visibility="{Binding ShowControls}"/>
</StackPanel>
The code behind MainPage.xaml.cs:
public partial class MainPage : UserControl
{
VisibilityData visibilityData = new VisibilityData();
public MainPage()
{
InitializeComponent();
VisibilityList.Items.Add(Visibility.Visible);
VisibilityList.Items.Add(Visibility.Collapsed);
this.DataContext = visibilityData;
}
}
And the data class:
public class VisibilityData : INotifyPropertyChanged
{
private Visibility showControls = Visibility.Visible;
public Visibility ShowControls
{
get { return showControls; }
set
{
showControls = value;
OnPropertyChanged("ShowControls");
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
public event PropertyChangedEventHandler PropertyChanged;
}
When you run that code you should get a ListBox with the options Visible and Collapsed and when you choose an option you should see the visibility of the button and checkbox is changed to reflect your choice. Let me know if that's not what you were looking for.
After thinking about this the solution occured to me, which is to construct the List in XAML then using a Converter on the Checkboxes to convert their Boolean IsChecked to the Property Visibility Property in the Class.
You can create a list such as:
<ComboBox Canvas.Left="6" Canvas.Top="69" Width="274" Height="25" Name="Display">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventStart,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event Start"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Display.EventEnd,Mode=TwoWay,Converter={StaticResource VisibilityConverter}}"/>
<TextBlock Text="Event End"/>
</StackPanel>
</ComboBox>
I can then Bind to the Two Properties I want (this example from the actual application).
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();
}