Can someone explain to me why the first property below (Name) updates fine from the UI, but the second one (End) does not? Both properties display correctly, so it IS bound. It just won't update the child property.
Period.Period (not my choice in the naming) is defined as a datetimeoffset.
<custom:FieldControl TargetObject="{Binding Path=Period}" TargetProperty="Name" IsReadOnly="False" />
<custom:FieldControl TargetObject="{Binding Path=Period.Period}" TargetProperty="End" IsReadOnly="False" />
I'm very new to XAML, so, if I haven't included enough detail, let me know and I'll edit the question.
You probably need to implement a INotifyPropertyChanged.
Here is an example with your two properties:
public class PeriodSample : INotifyPropertyChanged
{
private string name;
private DateTimeOffset period;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public DateTimeOffset Period
{
get
{
return period;
}
set
{
period = value;
OnPropertyChanged(nameof(Period));
}
}
public PeriodSample()
{
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
This concept allow to notify the Control when the binded properties changed.
If that doesn't solve the problem, could you provide a more complete example please?
Related
I'm currently refactoring a few abstraction layers into a Xamarin app in order to break the "monolithic" structure left by the previous dev, but something has gone awry. In my ViewModel, I have a few properties that call NotifyPropertyChange in order to update the UI whenever a value is picked from a list. Like so:
public Notifier : BindableObject, INotifyPropertyChanged
{
//...
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Had to create a middle layer due to my specific needs
public interface ISomeArea
{
DefinicaoServicoMobile TipoPasseio { get; set; }
}
-
public class SomeAreaImpl : Notifier, ISomeArea
{
//...
protected DefinicaoServicoMobile _tipoPasseio;
public DefinicaoServicoMobile TipoPasseio
{
get => _tipoPasseio;
set
{
if (_tipoPasseio != value)
{
_tipoPasseio = value;
NotifyPropertyChanged(nameof(TipoPasseio));
}
}
}
}
The actual bound view model:
public MyViewModel : BaseViewModel, ISomeArea
{
private SomeAreaImpl someArea;
//...
public MyViewModel()
{
// This is meant to provide interchangable areas across view models with minimal code replication
someArea = new SomeAreaImpl();
}
public DefinicaoServicoMobile TipoPasseio
{
get => someArea.TipoPasseio;
set => someArea.TipoPasseio = value;
}
}
And the .xaml snippet:
<renderers:Entry
x:Name="TxtTipoPasseio"
VerticalOptions="Center"
HeightRequest="60"
HorizontalOptions="FillAndExpand"
Text="{Binding TipoPasseio.DsPadrao}"
/>
The renderer opens a list allowing the user to choose whichever "TipoPasseio" they want, and supposedly fill the textbox with a DsPadrao (standard description). Everything works, even the reference to TipoPasseio is held after being selected (I know this because should I bring up the list a second time, it will only display the selected DsPadrao, giving the user the option to clean it. If he does, a third tap will show all the options again.
I might have screwed up in the abstraction, as I don't see the setter for myViewModel.TipoPasseio being called, tbh
Any ideas?
Let's reason through what Xamarin knows (as best as we can, since you didn't include all of the relevant code):
You have a data context having the type MyViewModel
That view model object has a property named TipoPasseio, having type DefinicaoServicoMobile
The type DefinicaoServicoMobile has a property named DsPadrao
It is that last property that is bound to the Entry.Text property.
In a binding, any observable changes to values forming the source or path for the binding will cause the runtime to update the target property for the binding (Entry.Text) and thus result in a change in the visual appearance (i.e. new text being displayed).
Note the key word observable. Here are the things I see which are observable by Xamarin:
The data context. But this doesn't change.
That's it.
With respect to the value of the MyViewModel.TipoPasseio property, there's nothing in the code you posted showing this property changing. But if it did, it doesn't look like MyViewModel implements INotifyPropertyChanged, so Xamarin wouldn't have a way to observe such a change.
On that second point, you do implement INotifyPropertyChanged in the SomeAreaImpl type. But Xamarin doesn't know anything about that object. It has no reference to it, and so has no way to subscribe to its PropertyChanged event.
Based on your statement:
I don't see the setter for myViewModel.TipoPasseio being called
That suggests that the TipoPasseio property isn't being changed. I.e. while you wouldn't be providing notification to Xamarin even if it did change, it's not changing anyway.
One property that does seem to be changing is the DsPadrao property (after all, it's the property that's actually providing the value for the binding). And while you don't provide enough details for us to know for sure, it seems like a reasonable guess that the DefinicaoServicoMobile doesn't implement INotifyPropertyChanged, and so there's no way for Xamarin to ever find out the value of that property might have changed either.
In other words, of all the things that Xamarin can see, the only one that it would be notified about of a change is the data context. And that doesn't seem to be what's changing in your scenario. None of the other values are held by properties backed by INotifyPropertyChanged.
Without a complete code example, it's impossible to know for sure what the right fix is. Depending on what's changing and how though, you need to implement INotifyPropertyChanged for one or more of your types that don't currently do so.
As it turns out, I wasn't firing the NotifyPropertyChanged of the correct object. Both MyViewModel and SomeAreaImpl implemented INotifyPropertyChanged per the Notifier class as BaseViewModel also extends from Notifier but that ended up ommited in my question. Having figured that out, here's an working (and complete) example:
public Notifier : BindableObject, INotifyPropertyChanged
{
//...
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Specifics about DefinicaoServicoMobile are negligible to this issue
public interface ISomeArea
{
//...
DefinicaoServicoMobile TipoPasseio { get; set; }
Task SetServico(ServicoMobile servicoAtual;
//...
}
For the sake of clarification
public abstract class BaseViewModel : Notifier
{
protected abstract Task SetServico(ServicoMobile servicoAtual);
public async Task SetServico()
{
//...
await SetServico(servicoAtual);
//...
}
}
Changed a couple of things here. It no longer extends from Notifier, which was kinda weird to begin with. Also this is where I assign TipoPasseio
public class SomeAreaImpl : ISomeArea
{
//...
protected DefinicaoServicoMobile _tipoPasseio;
// I need to call the viewModel's Notifier, as this is the bound object
private BaseViewModel viewModel;
public AreaServicosDependentesImpl(BaseViewModel viewModel)
{
this.viewModel = viewModel;
}
public DefinicaoServicoMobile TipoPasseio
{
get => _tipoPasseio;
set
{
if (_tipoPasseio != value)
{
_tipoPasseio = value;
viewModel.NotifyPropertyChanged(nameof(TipoPasseio));
}
}
}
//Assigning to the property
public async Task SetServico(ServicoMobile servicoAtual, List<DefinicaoServicoMobile> listDefinicaoServico)
{
//...
TipoPasseio = listDefinicaoServico
.FirstOrDefault(x => x.CdServico == servicoAtual.TpPasseio.Value);
//...
}
}
Changes to the view model:
public MyViewModel : BaseViewModel, ISomeArea
{
private SomeAreaImpl someArea;
//...
public MyViewModel()
{
someArea = new SomeAreaImpl(this);
}
public DefinicaoServicoMobile TipoPasseio
{
get => someArea.TipoPasseio;
set => someArea.TipoPasseio = value;
}
protected override async Task SetServico(ServicoMobile servicoAtual)
{
//...
someArea.SetServico(servicoAtual, ListDefinicaoServico.ToList());
//...
}
}
View model binding
public abstract class BaseEncerrarPontoRotaPage : BasePage
{
private Type viewModelRuntimeType;
public BaseEncerrarPontoRotaPage(Type viewModelRuntimeType)
{
this.viewModelRuntimeType = viewModelRuntimeType;
}
private async Task BindContext(PontoRotaMobile pontoRota, ServicoMovelMobile servicoMovel, bool finalizar)
{
_viewModel = (BaseViewModel)Activator.CreateInstance(viewModelRuntimeType, new object[] { pontoRota, UserDialogs.Instance });
//...
await _viewModel.SetServico();
//...
BindingContext = _viewModel;
}
public static BaseEncerrarPontoRotaPage Create(EnumAcaoServicoType enumType)
{
Type pageType = enumType.GetCustomAttribute<EnumAcaoServicoType, PageRuntimeTypeAttribute>();
Type viewModelType = enumType.GetCustomAttribute<EnumAcaoServicoType, ViewModelRuntimeTypeAttribute>();
return (BaseEncerrarPontoRotaPage)Activator.CreateInstance(pageType, new object[] { viewModelType });
}
}
Page instantiation is performed in some other view model, not related to the structure presented here
private async Task ShowEdit(bool finalizar)
{
await Task.Run(async () =>
{
var idAcaoServico = ServicoMobileAtual.DefinicaoServicoMobile.IdAcaoServico;
var page = BaseEncerrarPontoRotaPage.Create((EnumAcaoServicoType)idAcaoServico);
await page.BindContext(PontoRotaAtual, ServicoMovelMobileAtual, finalizar);
BeginInvokeOnMainThread(async () =>
{
await App.Navigation.PushAsync(page);
});
});
}
Codebehind:
public partial class MyPage : BaseEncerrarPontoRotaPage
{
public NormalUnidadePage() { }
public MyPage(Type viewModelType) : base(viewModelType)
{
InitializeComponent();
//Subscription to show the list
TxtTipoPasseio.Focused += TxtTipoPasseio_OnFocused;
//...
}
}
XAML
<views:BaseEncerrarPontoRotaPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:My.Name.Space.;assembly=Phoenix.AS"
x:Class="My.Name.Space.MyPage">
//...
<renderers:Entry
x:Name="TxtTipoPasseio"
VerticalOptions="Center"
HeightRequest="60"
HorizontalOptions="FillAndExpand"
Text="{Binding TipoPasseio.DsPadrao}"/>
//...
</views:BaseEncerrarPontoRotaPage>
I know could propagate an event from the AreaImpl classes in order to fire the Notify event in the view model, but right now I'm satisfied with this solution.
I am using Strings.resx, Strings.de.resx, etc. to localize Xamarin.Forms app.
I need to be able to change interface language at run time, and it (allmost) works.
Xamarin generates static class Strings in namespace MyProject.Resources from resource files, and I use those values to display strings on UI.
When doing it from code, it works flawlessly:
await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));
Problem is - not all attributes defined this way from XAML are updated when I change UI culture during runtime.
Buttons, Labels, Entry properties (Placeholder etc.) change as they should, but PageTitle, Toolbaritems, and some other properties remain in previous language.
I presume that some of these are populated when Page is first created, and are not updated on culture (and UI culture) change.
So, basically, I need a way to combine {DynamicResource ...} with values from resources.
I know that DynamicResource is ment to be used with Resource dictionary, but that is not a good way to store language translations for localization.
I tried
Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"
also not working.
Is there a way of refreshing page dynamicaly?
I also tried calling
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));
from Appearing event for that page, but that also does not work.
Any ideas?
Part of XAML file
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject.View"
xmlns:rs="clr-namespace:MMPI"
x:Class="MyProject.MainListPage"
xmlns:lr="clr-namespace:MyProject.Resources"
Title="{x:Static lr:Strings.appName}"
>
<ContentPage.ToolbarItems>
<ToolbarItem
Name="New"
Order="Primary"
Priority="0"
Text="{x:Static lr:Strings.lblAddNew}"
Clicked="New_Clicked"
>
When i encountered that challenge in a project I resolved it by using a simple class ResourceLoader and making use of INotifyPropertyChanged.
You can access the Instanceproperty from anywhere and change the culture. All String that are bound to the index would update.
The ResourceManager instance injected into the constructor must be set up appropriately.
public class ResourceLoader : INotifyPropertyChanged
{
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
public static ResourceLoader Instance { get; private set; }
public string GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
return stringRes;
}
public string this[string key] => this.GetString(key);
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
To display the localized strings in your application you need to bind through the indexer like so:
<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />
Since it is now bound it should update when you call ResourceLoader.SetCultureInfo because the Item[] 'PropertyName' is causing bound controls to re-fetch the values to their bound keys.
Update
I just tested it if i was talking bogus and for some reason the property changed didn't work. I've added a different approach below, which is close to what i'm using in production i urge you to add some kind of weak reference 'caching' instead of the simple list holding all the string resources (otherwise they will be kept forever)
I'm keeping above for reference.
public class ResourceLoader
{
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
private readonly List<StringResource> resources = new List<StringResource>();
public static ResourceLoader Instance { get; private set; }
public StringResource this[string key] {
get { return this.GetString(key); }
}
public StringResource GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
var stringResource = new StringResource(resourceName, stringRes);
this.resources.Add(stringResource);
return stringResource;
}
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
foreach (StringResource stringResource in this.resources) {
stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
}
}
}
StringResource:
public class StringResource : INotifyPropertyChanged
{
public StringResource(string key, string value)
{
this.Key = key;
this.Value = value;
}
private string value;
public string Key { get; }
public string Value {
get { return this.value; }
set {
this.value = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Binding
<Label Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}"
/>
Update 2
Came across this link where they implemented it similarly to my first approach. Maybe you can give it a try.
Update 3
Fixed the first approach. Both are working now. What was needed was this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); instead of this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));
I solved it very similar to #woelliJ . I just wanted to have key as strongly types from static class and binding should be in code behind.
ITranslationService is singleton from static variable. It is very close like #woelliJ .
[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
private readonly ITranslationService? _translationService;
public TranslateExtension()
{
_translationService = Mobile.App.TranslationService;
}
public string? Text { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var translationItem = _translationService[Text];
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"Value",
Source = translationItem,
};
return binding;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
}
[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
public string? Key { get; set; }
public string? Value { get; set; }
}
Then label would be like this
<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />
Say we have a grid view which is binding with the data source MyInformation. One of column is a check box. I want to bind something with it.
ItemsSource="{Binding MyInformation}"
In the ViewModel.
public ObservableCollection<Container> MyInformation
{
get
{
if (this.myInformation == null)
{
this.myInformation = new ObservableCollection<Container>();
}
return this.myInformation;
}
set
{
if (this.myInformation != value)
{
this.myInformation = value;
this.OnPropertyChanged("MyInformation");
}
}
}
The class Container has a member "GoodValue".
public class Container
{
public bool GoodValue {get;set;}
//
}
I have the checkbox bind with the member.
<DataTemplate>
<CheckBox HorizontalAlignment="Center" IsChecked="{Binding GoodValue, Converter={StaticResource ShortToBooleanConverter}}" Click="CheckBox_Checked"></CheckBox>
</DataTemplate>
I don't have the property GoodValue created in ViewModel as I think GoodValue is a member of Container. The ObservableCollection includes it automatically.
The problem is each time I read the data from the database. The checkbox is unchecked. So I doubt my code. Thanks for hint.
You can do two things:
Check if there are some binding errors
Implement INotifyPropertyChanged interface into your class Container.
public class Container:INotifyPropertyChanged
{
private bool _goodValue;
public string GoodValue
{
get
{
return _goodValue;
}
set
{
_goodValue = value;
OnPropertyChanged("GoodValue");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ObservableCollection is usefull if you want to notify to your view when a new item is inserted or deleted from the collection, but if the object contained inside it doesn't implement InotifyPropertyChanged, the changes to properties of that object won't affect any change to your view.
I want to create custom text box with attached property for Windows Store app. I am following this solution. Now it uses hard coded value as property value but I want to set value using binding, but it's not working. I tried to search a lot but didn't helped me any solution.
The exception details is like this
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException'
occurred in CustomTextBox.exe but was not handled in user code
WinRT information: Failed to assign to property
'CustomTextBox.Input.Type'.
MainPage.xaml
<!-- local:Input.Type="Email" works -->
<!-- local:Input.Type="{Binding SelectedTextboxInputType}" not working -->
<TextBox x:Name="txt" local:Input.Type="{Binding SelectedTextboxInputType}" Height="30" Width="1000" />
<ComboBox x:Name="cmb" ItemsSource="{Binding TextboxInputTypeList}" SelectedItem="{Binding SelectedTextboxInputType}" Height="30" Width="200"
Margin="451,211,715,527" />
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel();
}
}
Input.cs
//InputType is enum
public static InputType GetType(DependencyObject obj)
{
return (InputType)obj.GetValue(TypeProperty);
}
public static void SetType(DependencyObject obj, InputType value)
{
obj.SetValue(TypeProperty, value);
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is InputType)
{
var textBox = (TextBox)d;
var Type = (InputType)e.NewValue;
if (Type == InputType.Email || Type == InputType.URL)
{
textBox.LostFocus += OnLostFocus;
}
else
{
textBox.TextChanged += OnTextChanged;
}
}
}
ViewModel.cs
public class ViewModel : BindableBase
{
public ViewModel()
{
TextboxInputTypeList = Enum.GetValues(typeof(InputType)).Cast<InputType>();
}
private InputType _SelectedTextboxInputType = InputType.Currency;
public InputType SelectedTextboxInputType
{
get { return _SelectedTextboxInputType; }
set { this.SetProperty(ref this._SelectedTextboxInputType, value); }
}
private IEnumerable<InputType> _TextboxInputTypeList;
public IEnumerable<InputType> TextboxInputTypeList
{
get { return _TextboxInputTypeList; }
set { this.SetProperty(ref this._TextboxInputTypeList, value); }
}
}
This is a pretty common mistake. The problem is, binding targets cannot be CLR properties in XAML. It's just the rules. A binding source can be a CLR property, just fine. The targets simply must be dependency properties.
We all get the error! :)
I describe the whole thing here: http://blogs.msdn.com/b/jerrynixon/archive/2013/07/02/walkthrough-two-way-binding-inside-a-xaml-user-control.aspx
Best of luck.
Incorrect
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
Correct
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(Input), new PropertyMetadata(default(InputType), OnTypeChanged));
I have two ComboBoxes, A & B, each bound to an Observable Collection. Each has a SelectionChanged trigger is attached which is intended to catch when the user changes a selection. The trigger passes the selection to a Command.
The collections implement INotifyPropertyChanged in that, in the Setter of each, an NotifyPropertyChanged event is fired. This is needed (in the MVVM approach) to notify the UI (the View) that the ComboBox's contents have changed.
The two ComboBoxes are interdependent - changing the selection in A causes B to be repopulated with new items.
Now, the problem is that B's SelectionChanged trigger fires in response to its collection being repopulated (as well as the user changing a selection). Due to the complexity of the code in the Command this is a huge waste of resources.
I could in theory stop this by not raising the NotifyPropertyChanged event when B's collection is set (because, looking at the Call Stack, this is what seems to cause the SelectionChanged trigger to fire), however the MVVM approach depends on this to keep the UI refreshed.
Any suggestions?
Why does ComboB need a SelectionChanged event? You can just bind the selected item directly into a property on the VM.
The way i have tackled this previously was to bind ComboA's selected item into the VM. In the setter for that property, i recalculate the available items for ComboB and assign them to another property on the VM, and ComboB's ItemsSource is bound to this property. Of course that property will notify (using INotifyPropertyChanged), but nothing else needed to be done, my ComboB did not have a SelectionChanged event. By using this method i didn't need a SelectionChanged on ComboA either, which keeps the view's code behind nice and sparse - everything is handled in the VM and regular databinding takes care of the rest.
Edit:
Here is an example of adjusting the required lists from within the property setters:
public class MyViewModel : INotifyPropertyChanged
{
//ItemsSource of ComboA is bound to this list
public List<SomeObject> ComboAList
{
get { return _comboAList; }
set { _comboAList = value; }
}
//ItemsSource of ComboB is bound to this list
public List<SomeObject> ComboBList
{
get { return _comboBList; }
set
{
_comboBList = value;
OnPropertyChanged("ComboBList");
}
}
//ItemsSource of the dataGrid is bound to this list
public List<SomeObject> DataGridList
{
get { return _datagridList; }
set
{
_datagridList = value;
OnPropertyChanged("DataGridList");
}
}
//SelectedItem of ComboA is bound to this property
public SomeObject FirstSelectedItem
{
get { return _firstSelectedItem; }
set
{
_firstSelectedItem = value;
RefreshListForComboB();
}
}
//SelectedItem of ComboB is bound to this property
public SomeObject SecondSelectedItem
{
get { return _secondSelectedItem; }
set
{
_secondSelectedItem = value;
RefreshListForDataGrid();
}
}
private void RefreshListForComboB()
{
//do whatever is necessary to filter or create a list for comboB
ComboBList = doSomethingThatReturnsAListForComboB();
}
private void RefreshListForDataGrid()
{
//do whatever is necessary to filter or create the list for the DataGrid
DataGridList = doSomethingThatReturnsAListForDataGrid();
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private List<SomeObject> _comboAList, _comboBList, _datagridList;
private SomeObject _firstSelectedItem, _secondSelectedItem;
}
And here is a slightly different way to do it, using a PropertyChange event handler on the VM, this simply changes where the list updating happens. This is arguably a better way of doing it than the first sample as it means the property setters don't have side effects:
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
this.PropertyChanged += new PropertyChangedEventHandler(MyViewModel_PropertyChanged);
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "FirstSelectedItem":
RefreshListForComboB();
break;
case "SecondSelectedItem":
RefreshListForDataGrid();
break;
}
}
//ItemsSource of ComboA is bound to this list
public List<SomeObject> ComboAList
{
get { return _comboAList; }
set { _comboAList = value; }
}
//ItemsSource of ComboB is bound to this list
public List<SomeObject> ComboBList
{
get { return _comboBList; }
set
{
_comboBList = value;
OnPropertyChanged("ComboBList");
}
}
//ItemsSource of the dataGrid is bound to this list
public List<SomeObject> DataGridList
{
get { return _datagridList; }
set
{
_datagridList = value;
OnPropertyChanged("DataGridList");
}
}
//SelectedItem of ComboA is bound to this property
public SomeObject FirstSelectedItem
{
get { return _firstSelectedItem; }
set
{
_firstSelectedItem = value;
OnPropertyChanged("FirstSelectedItem");
}
}
//SelectedItem of ComboB is bound to this property
public SomeObject SecondSelectedItem
{
get { return _secondSelectedItem; }
set
{
_secondSelectedItem = value;
OnPropertyChanged("SecondSelectedItem");
}
}
private void RefreshListForComboB()
{
//do whatever is necessary to filter or create a list for comboB
ComboBList = doSomethingThatReturnsAListForComboB();
}
private void RefreshListForDataGrid()
{
//do whatever is necessary to filter or create the list for the DataGrid
DataGridList = doSomethingThatReturnsAListForDataGrid();
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private List<SomeObject> _comboAList, _comboBList, _datagridList;
private SomeObject _firstSelectedItem, _secondSelectedItem;
}