Windows Store Apps App Bar Buttons Text Binding - c#

I have an App Bar with some buttons like this
<Page.BottomAppBar>
<AppBar x:Name="bottomAppBar" Padding="10,10,10,10" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource ReadAppBarButtonStyle}" >
</Button>
</StackPanel>
</AppBar>
</Page.BottomAppBar>
I want to bind the button text to a selected item property of a ListView and use an IValueConverter.
I found that the button text is to be set using AutomationProperties.Name
how can I bind this property through XAML or Code.
Thanks

You're right, for some reason the following doesn't work, although the same binding works just fine you use it for e.g. Text property of a TextBox:
<Button Style="{StaticResource SkipBackAppBarButtonStyle}" AutomationProperties.Name="{Binding SelectedItem, ElementName=List}" />
I did manage to get it work by using a property in the view model and binding to it both ListView.SelectedItem and AutomationProperties.Name:
<ListView ItemsSource="{Binding Strings}"
SelectedItem="{Binding SelectedString, Mode=TwoWay}" />
<!-- ... -->
<Button Style="{StaticResource SkipBackAppBarButtonStyle}"
AutomationProperties.Name="{Binding SelectedString}" />
SelectedString should be a property in a view model implementing INotifyPropertyChanged:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Strings = new ObservableCollection<string>();
for (int i = 0; i < 50; i++)
{
Strings.Add("Value " + i);
}
}
public ObservableCollection<string> Strings { get; set; }
private string _selectedString;
public string SelectedString
{
get { return _selectedString; }
set
{
if (value == _selectedString) return;
_selectedString = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

ComboBox Selected Item not updating

Problem
I am trying to bind a ComboBox's SelectedItem to a custom class but this does not update when the property is changed.INotifyPropertyChanged is implemented.
The DataContext
The DataContext is a custom class which contains many properties, but an extract of this is below. You can see it implements INotifyPropertyChanged and this called when the two properties are changed.
public class BctsChange : INotifyPropertyChanged
{
#region declarations
private byContact _Engineer;
public byContact Engineer
{
get { return _Engineer; }
set
{
_Engineer = value;
NotifyPropertyChanged("Engineer");
OnEngineerChanged();
}
}
private BctsSvc.DOSets _LeadingSet;
public BctsSvc.DOSets LeadingSet
{
get { return _LeadingSet; }
set { _LeadingSet = value; NotifyPropertyChanged("LeadingSet"); }
}
#endregion
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public BctsChange()
{
Engineer = new byContact(Environment.UserName);
}
private void OnEngineerChanged()
{
if (Engineer != null)
{
BctsSvc.DOSets leadSet = GetLeadingSetFromDeptCode(Engineer.DeptCode);
if (leadSet == null) return;
LeadingSet = leadSet;
}
}
private static BctsSvc.DOSets GetLeadingSetFromDeptCode(string DeptCode)
{
BctsSvc.BctsServiceSoapClient svc = new BctsSvc.BctsServiceSoapClient();
BctsSvc.DOSets setX = svc.GetSetFromDeptCode(DeptCode);
return setX;
}
}
The Window XAML
I have several controls on the window, but to keep the code simple I believe the following extract will suffice.
<Window x:Class="MyNamespace.wdSubmit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:MyNamespace"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="ucReqForm"
Title="wdSubmit" >
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch">
<GroupBox Header="Engineer Details" Name="grpOwnerDetails" >
<StackPanel Orientation="Vertical">
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="35"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Engineer.FullName, FallbackValue='Please select an engineer by clicking →', Mode=OneWay}" Margin="5,0" IsEnabled="True" FontStyle="Italic" />
<Button Content="{StaticResource icoSearch}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" Height="23" Name="btnSelectEngineer" Margin="0,0,5,0" HorizontalAlignment="Stretch" ToolTip="Search for an engineer responsible" Click="btnSelectEngineer_Click" />
</Grid>
<ComboBox Height="23" x:Name="ddSet2" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<my:LabelledDropdown Height="23" x:Name="ddSet" Margin="5,0" ItemsSource="{Binding LeadingSets, Mode=OneWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" SelectedItem="{Binding LeadingSet, Mode=TwoWay,NotifyOnTargetUpdated=True,NotifyOnSourceUpdated=True}" Label="e.g. BodyHardware">
<my:LabelledDropdown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetName}" ToolTip="{Binding HelpInfo}"/>
</DataTemplate>
</my:LabelledDropdown.ItemTemplate>
</my:LabelledDropdown>
</StackPanel>
</GroupBox>
</StackPanel>
</Window>
The above extract contains:
A Label that contains a contact's name, and a button to search for a contact, bound to the FullName of the Engineer
A ComboBox that contains departments within the company, bound to an ObservableCollection<DOSets>, which contains a list of departments
Two ComboBoxes, one which is a custom one and the other which is temporary to ensure the bug is not within the control. These are Databound to LeadingSet
Window Code Behind
In the code behind I set the DataContext to CurrentChange. When the user wants to select a different Engineer then this will update the selected department for the engineer in CurrentChange.
When the user changes the engineer, the data binding for the engineer is updated, but the selected department (Leading Set) isn't.
//Usings here
namespace MyNamespace
{
public partial class wdSubmit : Window, INotifyPropertyChanged
{
private BctsSvc.BctsServiceSoapClient svc;
private BctsChange _CurrentChange;
public BctsChange CurrentChange
{
get { return _CurrentChange; }
set { _CurrentChange = value; OnPropertyChanged("CurrentChange"); }
}
private List<BctsSvc.DOSets> _LeadingSets;
public List<BctsSvc.DOSets> LeadingSets
{
get
{
return _LeadingSets;
}
}
public wdSubmit()
{
InitializeComponent();
svc = new BctsSvc.BctsServiceSoapClient();
_LeadingSets = svc.GetLeadSets().ToList();
OnPropertyChanged("LeadingSets");
this._CurrentChange = new BctsChange();
this.DataContext = CurrentChange;
CurrentChange.PropertyChanged += new PropertyChangedEventHandler(CurrentChange_PropertyChanged);
}
void CurrentChange_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("CurrentChange");
OnPropertyChanged(e.PropertyName);
}
private void btnSelectEngineer_Click(object sender, RoutedEventArgs e)
{
byContact newContact = new frmSearchEngineer().ShowSearch();
if (newContact != null)
{
CurrentChange.Engineer = newContact;
PropertyChanged(CurrentChange, new PropertyChangedEventArgs("LeadingSet"));
PropertyChanged(CurrentChange.LeadingSet, new PropertyChangedEventArgs("LeadingSet"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(CurrentChange, new PropertyChangedEventArgs(propertyName));
}
}
}
I've realised the problem may be due to the LeadingSet, returned when the engineer is changed, being a different instance to that in the ObservableCollection.

WPF ~ Trouble with Binding & INotifyPropertyChanged

WPF n00bie here, trying to get his UI to work properly.
So I made this test example. The textblock bound to HeaderText1 changes correctly at the launch of the app, but the textblock bound to HeaderText2 doesn't update after clicking the button.
What am I doing wrong? Thanks in advance!!
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=DataContext.HeaderText2}"></TextBlock>
</StackPanel>
</Window>
Main window class:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DataBinding
{
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
public DataContextSample()
{
HeaderText = "YES";
InitializeComponent();
this.DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
Source source = new Source();
source.HeaderText2 = "YES2";
}
}
}
And the INotifyPropertyChanged class
using System.ComponentModel;
namespace DataBinding
{
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set; get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
}
First of all you are doing many things wrong.
You should not be using the window as it's own datacontext, you should have a viewmodel that you set.
You should not be using event handlers in the view to manipulate the viewmodel. You should bind the button to a command.
Your source seems to be a "viewmodel", consider renaming it to MainWindowViewModel (for clarity) and then do this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private string headerText;
private string headerText2;
private ICommand updateHeaderText2;
public string HeaderText
{
set
{
return this.headerText;
}
get
{
this.headerText = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText");
}
}
public string HeaderText2
{
set
{
return this.headerText2;
}
get
{
this.headerText2 = value;
// Actually raise the event when property changes
this.OnPropertyChanged("HeaderText2");
}
}
public ICommand UpdateHeaderText2
{
get
{
// Google some implementation for ICommand and add the MyCommand class to your solution.
return new MyCommand (() => this.HeaderText2 = "YES2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And set this viewmodel to the datacontext of your window.
this.DataContext = new MainWindowViewModel();
And then in your xaml you should bind to the viewmodel as such
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<!-- Not sure what this binding is? -->
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Command="{Binding UpdateHeaderText2}" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding HeaderText}"></TextBlock>
<TextBlock Text="{Binding HeaderText2}"></TextBlock>
</StackPanel>
</Window>
You set the DataContext to this (the window). You don't have a property named HeaderText2 in the DataContext so the second binding won't work.
I'd do this (without changing your code too much, in reality I'd do a proper MVVM approach):
public partial class DataContextSample : Window
{
public Source Source { get; set; }
public string HeaderText { set; get; }
public MainWindow()
{
InitializeComponent();
HeaderText = "YES";
Source = new Source { HeaderText2 = "YES" };
DataContext = this;
}
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
BindingExpression binding = txtWindowTitle.GetBindingExpression(TextBox.TextProperty);
if (binding != null)
{
binding.UpdateSource();
}
Source.HeaderText2 = "YES2";
}
}
I added a new property called Source which is of type Source. Set its initial HeaderText2 to the same "YES" in the constructor and in the button click change that to "YES2".
You have to change your Source class as well, to actually notify about changes:
public class Source : INotifyPropertyChanged
{
private string _headerText2;
public string HeaderText2
{
get { return _headerText2; }
set
{
_headerText2 = value;
OnPropertyChanged("HeaderText2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And then in your XAML:
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Text="{Binding Path=Source.HeaderText2}"></TextBlock>
</StackPanel>
Well there are a few issues with your code.
First of all, you never assign your "Source" to a datacontext, so there's no way for your second TextBlock to find the value of "HeaderText2".
If however you would assign your "Source" to the textblocks datacontext then we could fetch the value of "HeaderText2". Consider the code below
<Window x:Class="DataBinding.DataContextSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataContextSample" Height="142.596" Width="310">
<StackPanel Margin="15">
<WrapPanel>
<TextBlock Text="Window title: " />
<TextBox Name="txtWindowTitle" Text="{Binding Title, UpdateSourceTrigger=Explicit}" Width="150" />
<Button Name="btnUpdateSource" Click="btnUpdateSource_Click" Margin="5,0" Padding="5,0">*</Button>
</WrapPanel>
<TextBlock Text="{Binding Path=HeaderText}"></TextBlock>
<TextBlock Name="TextBlock2" Text="{Binding Path=HeaderText2}"></TextBlock>
</StackPanel>
</Window>
We have given your second Textblock a name, "TextBlock2" and also removed the "Datacontext"-part from your binding.
Then we have moved the Creation of your "Source" object from the button event to the windows constructor (there is no need to make a new one everytime we click a button when all we want to do is to update a property)
public partial class DataContextSample : Window
{
public string HeaderText { set; get; }
private Source source { get; set; }
public DataContextSample()
{
...
source = new Source();
TextBlock2.DataContext = source;
...
}
...
}
And then in your buttons click-event we assign your databound property a value of "YES2".
private void btnUpdateSource_Click(object sender, RoutedEventArgs e)
{
...
source.HeaderText2 = "YES2";
}
There is however one more detail. Your class "Source" does implement "INotifyPropertyChanged", but it never "uses" it. By that I mean, that when you assign a value to your property "HeaderText2" you never actually "notify" the UI that something has changed with it, and thus the UI will not fetch the new value. Consider the code below:
public class Source : INotifyPropertyChanged
{
public string HeaderText2 { set
{
headerText2 = value;
OnPropertyChanged("HeaderText2");
}
get
{
return headerText2;
}
}
string headerText2;
...
}
So let's take a look at what we've done with the property "HeaderText2". Everytime the "HeaderText2" gets a value assigned, it will first save the value in a privat property (so that we can read from it later). But in addition to that we also call the "OnPropertyChanged" method with our Propertys name. That method will in turn check if anyone is "listening" to our "PropertyChanged"-event (and since we have a databinding on the current object, someone is listening), and create a new event.
Now we have assigned a datasource to your textblock with a path to "HeaderText2", we are notifying all listeners when we update "HeaderText2" on the datasource and we are updating "HeaderText2" on the buttons click event.
Happy coding!

Textblock's text value not updating in View when changed in ViewModel

I made a submit button with the command that changes the text in a textblock based on whether the user entered the correct number in a textbox.
public string txtResults { get; set; }
public string txtInput { get; set; }
// Method to execute when submit command is processed
public void submit()
{
if (txtInput == number.ToString())
txtResults = "Correct!";
else
txtResults = "Wrong!";
}
'txtInput' is the member that is bound to the textbox and includes the user's input. 'txtResults' is supposed to be shown in the textblock. Right now when I click the submit button, in debugging mode, the txtResults value is assigned the "Correct!" string but it doesn't update in the view.
The XAML:
<Window x:Class="WpfMVVP.WindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMVVP"
Title="Window View" Height="350" Width="525" Background="White">
<Grid>
<Canvas>
<Label Canvas.Left="153" Canvas.Top="89" Content="Guess a number between 1 and 5" Height="28" Name="label1" />
<TextBox Text="{Binding txtInput, UpdateSourceTrigger=PropertyChanged}" Canvas.Left="168" Canvas.Top="142" Height="23" Name="textBox1" Width="38" />
<TextBlock Text="{Binding txtResults}" Canvas.Left="257" Canvas.Top="142" Height="23" Name="textBlock1" />
<Button Command="{Binding Submit}" Canvas.Left="209" Canvas.Top="197" Content="Submit" Height="23" Name="button1" Width="75" />
</Canvas>
</Grid>
Update:
I made this change in my View Model
public class WindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private string _txtResults;
public string txtResults
{
get { return _txtResults; }
set { _txtResults = value; OnPropertyChanged("txtResults"); }
}
And now it's working! Thanks.
Please make sure your txtResults property is inheriting from INotifyPropertyChanged. Your view model should inherit from there also. Have your view model class inherit from INotifyPropertyChanged, and implement the interface. Then replace you TxtResults property with the following:
private string _txtResults = string.Empty;
public string TxtResults
{
get { return this._txtResults; }
set
{
this._txtResults= value;
this.RaisePropertyChangedEvent("TxtResults");
}
}

How to bind the view model to the view

I am having trouble binding my View Model to my View. I am a beginner with MVVM, but I believe I am implementing my system (almost) correctly. I have a Model that contains data, which I am getting in my View Model, and then when my page is navigated to, I am attempting to grab that View Model data and binding it to the View.
My issue is that I have a ListBox in my View with 3 objects per item, and I cannot seem to bind to it correctly for each item in my list.
MainPage.xaml
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}"
SelectionChanged="FavoritesListBox_SelectionChanged">
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}"
Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}"
FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress"
Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</ListBox>
MainPage.xaml.cs
public FavoritesPage()
{
InitializeComponent();
// Set the data context of the listbox control to the sample data
FavoritesListBox.DataContext = App.ViewModel;
}
App.xaml.cs
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainViewModel.cs
public ObservableCollection<ItemViewModel> FavoriteItems { get; private set; }
public MainViewModel()
{
//FavoriteItems = new ObservableCollection<ItemViewModel>();
FavoriteItems = Settings.FavoritesList.Value;
}
Settings.cs (The Model)
public static Setting<ObservableCollection<ItemViewModel>> FavoritesList =
new Setting<ObservableCollection<ItemViewModel>>(
"Favorites",
new ObservableCollection<ItemViewModel>());
ItemViewModel.cs
private string _favicon;
public string Favicon
{
get
{
return _favicon;
}
set
{
if (value != _favicon)
{
_favicon = value;
NotifyPropertyChanged("Favicon");
}
}
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private string _address;
public string Address
{
get
{
return _address;
}
set
{
if (value != _address)
{
_address = value;
NotifyPropertyChanged("Address");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
..and this is where and how I am saving each item (which should have three properties listed in the ItemViewModel
void addToFavorites_Click(object sender, EventArgs e)
{
var favoriteItem =
new ItemViewModel{
Favicon = "",
Name = "",
Address = TheBrowser.currentUrl() };
Settings.FavoritesList.Value.Add(favoriteItem);
}
Where FavoritesList is populated using an ItemViewModel containing 3 objects. The list is being populated correctly because during debugging I can see the entities in FavoritesList, but I am having an issue calling these entities in the view model to show up in my ListBox in the view?
I believe I am binding incorrectly but I'm not sure how to fix this?
In your XAML you bind to paths Name and Address do you have these 2 properties defined in your ItemViewModel?
Update after reading your code properly:
You are not updating the datatemplate of the Items of the Listbox. This is what you need to do:
<ListBox x:Name="FavoritesListBox" ItemsSource="{Binding FavoriteItems}" SelectionChanged="FavoritesListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="12,0,12,0">
<Image x:Name="favicon" Source="{Binding Favicon}" Width="50" Height="50"/>
<StackPanel>
<TextBlock x:Name="favoritesName" Text="{Binding Name}" FontSize="{StaticResource PhoneFontSizeExtraLarge}"/>
<TextBlock x:Name="favoritesAddress" Text="{Binding Address}" Margin="12,0,0,0"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In addition to setting your DataContext to your viewmodel, (as mentioned in the comment linking to Creating ContextBinding XAML) , you also need to have your view model implement INotifyPropertyChanged (including ItemViewModel, which you don't show in your question)

How can I access a label from a stackpanel that is a listBoxItem

For a list box in WPF I have this template:
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="Label" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
<Button Content="Edit" Width="Auto" Padding="1" Margin="2.5,0" HorizontalAlignment="Right" Click="Button_Click"/>
</StackPanel>
</ControlTemplate>
And whenever I press the corresponding button for the listBoxItem I want to modify the label of the same listBoxItem, preferably without using a name if possible.
I was thinking maybe there is a way of saying "use the Label from the parent of this button" which I thought would be the StackPanel, but can't find anything useful on the internet.
I think the better solution is to use a view model with a DataTemplate, once you have the code set up you can re-use it over and over with very little chance of error.
Here is what your view model will look like
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ItemViewModel> _items;
public ViewModel()
{
_items = new ObservableCollection<ItemViewModel>(new List<ItemViewModel>()
{
new ItemViewModel() { Label = "Item1", IsChecked = false },
new ItemViewModel() { Label = "Item2", IsChecked = true },
new ItemViewModel() { Label = "Item3", IsChecked = true },
new ItemViewModel() { Label = "Item4", IsChecked = false },
new ItemViewModel() { Label = "Item5", IsChecked = false },
});
}
public ObservableCollection<ItemViewModel> Items
{
get
{
return this._items;
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class ItemViewModel : INotifyPropertyChanged
{
private bool _isChecked = false;
private string _label = "Label";
public ICommand ButtonCommand { get; private set; }
public ItemViewModel()
{
this.ButtonCommand = new DelegateCommand(Com_ButtonCommand);
}
public void Com_ButtonCommand(object parameter)
{
this.Label = "New Label text";
}
public string Label
{
get
{
return this._label;
}
set
{
this._label = value;
this.OnPropertyChanged("Label");
}
}
public bool IsChecked
{
get
{
return this._isChecked;
}
set
{
this._isChecked = value;
this.OnPropertyChanged("IsChecked");
}
}
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
{
return true;
}
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
There are 3 classes here, 1 of them a helper.
ViewModel --> Your main ViewModel,
ItemViewModel --> Model for each item,
DelegateCommand --> Allows you to map the button to the view model
your xaml will look like this
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox IsChecked="{Binding IsChecked}" Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="{Binding Label}" Padding="5,0" Width="260" VerticalAlignment="Center" Background="#F3D6D6D6" Margin="5,0"/>
<Button Command="{Binding ButtonCommand}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Notice the "{Binding}" keyword, this "binds" each datatemplate to a member with that name on its own view model, in this case IsChecked and Label.
To load your ViewModel add the following line to the code-behind in your usercontrol(using MVVM you will rarely touch the code-behind of usercontrols at all).
this.DataContext = new ViewModel();
When its your first time seeing a viewmodel it may seem like a lot of work but its mostly re-usable and is the defacto standard for doing things like this (MVVM), Ive included all necessary code to get you started.
The following class as well as DelegateCommand should be kept for later use, I have already included it in the above snippet
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
I'd recommend you to use a viewmodel in the back. That VM exposes a command that is bound to the button. In addition it exposes a DependencyProperty containing the name for the label. And the label is bound to that property.
Now if you press the button, the command is executed, which changes the label text and via databinding the new text will be updated on the label.
The other option, I wouldn't recommend, is to use FindName to find the Label. Or really bad (but works) is to iterate over the controls using the VisualTreeHelper.
I would navigate the VisualTree to find the parent StackPanel, then search that StackPanel to find a child Label to update.
If you're interested, I have some VisualTreeHelpers posted on my blog that would make this easy:
var parent = VisualTreeHelpers.FindAncestor<StackPanel>((Button)sender);
if (parent == null) return;
var lbl = VisualTreeHelpers.FindChild<Label>(parent);
if (lbl == null) return;
lbl.Content = "Some Text";
This is providing I'm not using the MVVM design pattern. If I were using MVVM, I would be storing the Label.Content property in the ViewModel, and the Button command should point to a Command in the ViewModel, and it should pass it the DataBound item as the CommandParameter so it knows which Label to update.
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<StackPanel Orientation="Horizontal" Background="Silver">
<CheckBox Content="CheckBox" VerticalAlignment="Center"/>
<Label Content="{Binding SomeText}" ... />
<Button Content="Edit"
Command="{Binding ElementName=MyListBox, Path=DataContext.EditCommand}"
CommandParameter="{Binding }" />
</StackPanel>
</ControlTemplate>

Categories