See my CustomView window below
When I select project from the combobox, the client associated with that project should automatically displayed there.
In Combobox's selection changed event, I did so far
private string client
{
get
{
return ClientText.Text;
}
set
{
ClientText.Text = value;
}
}
public Harvest_Project projectClass
{
set
{
ProjectText.Text = value.ToString();
Harvest_Project proj = (Harvest_Project)ProjectText.Text; // shows error here. casting is not possible. What can I do here?
this.client = Globals._globalController.harvestManager.getClientEntriesThroughId(proj._client_id)._name;
PropertyChanged(this, new PropertyChangedEventArgs("client"));
}
}
public int project
{
get
{
return int.Parse(ProjectText.Text);
}
set
{
ProjectText.Text = value.ToString();
}
}
private void ProjectComboBoxChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ComboBoxItem)
{
ComboBoxItem item = (ComboBoxItem)sender;
}
}
In xaml I used binding like this,
<ComboBox x:Name="ProjectText" SelectionChanged="ProjectComboBoxChanged" ItemsSource="{Binding Path=projectList}" SelectedValuePath="_id" DisplayMemberPath="_name" SelectedItem="{Binding ProjectComboBoxChanged, Mode=OneWayToSource}" Background="Yellow" BorderThickness="0" Width="66"/>
In your event handler ProjectComboBoxChanged(object sender, SelectionChangedEventArgs e), sender is of type ComboBox and not ComboBoxItem, hence your if statement is always false.
e.AddedItems[0] will give you your desired ComboBoxItem. Make sure you check the count first.
Also, if all you want to do is to set the Text, you don't need to have the client property.
"client" is a property, it should be public.
Then PropertyChanged should be raised in the setter, so anytime you change client, the UI will know.
About the combo, SelectedItem should bind to a property, not a method. The property could be "client", but another property could be clearer.
In the setter of this property, you'll have the liberty to fix the new value of the "client" property.
And last, since you are using a binding for selectedItem, I see no reason to use the event selectionChanged. Use binding or event, not both.
Hope it helps ;)
Related
I have a question, I have a view and in that view I am having combobox:
<ComboBox ItemsSource="{Binding ProjectsBrowserAxmModules}" SelectedValuePath="AxmModuleId" DisplayMemberPath="AxmModuleName"
SelectedValue="{Binding SelectedAxmModule, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
controls:TextBoxHelper.Watermark="{Binding BrowserComboBoxWatermark}" Height="2" IsSynchronizedWithCurrentItem="True"
SelectionChanged="ComboBox_CurrentBrowserAxmChanged" >
And so event looks like:
private void ComboBox_CurrentBrowserAxmChanged(object sender, RoutedEventArgs e)
{
((CurrentsHistoryViewModel)DataContext).GetCurrentsModuleCommand.Execute(sender);
}
And every time i change view to different one and back to this view it looks like this event is registering anew so for example if i go to different view go back and go different view and back again this event will fire 4 times.
I tried
Loaded -= ComboBox_CurrentBrowserAxmChanged;
But no luck is there any way to unregistered those redundant events.
I believe, combobox SelectedValue property gets changed somehow and internally it calls your combobox selection changed event. have debugger point in setter of SelectedAxmModule property. hope you will find given property is hitting when you are switching screens.
My suggestion is , to remove selectionChanged event. and use event/delegate for same purpose.
Code Snipt:
public class AxmModule: NotifierModel
{
public static event MyEventHandler ValueChanges;
public delegate void MyEventHandler(string Value);
private string _selectedAxmModule ;
public string SelectedAxmModule
{
get
{
return _selectedAxmModule ;
}
set
{
_selectedAxmModule = value;
if (ValueChanged!= null)
{
ValueChanged(_selectedAxmModule );
RaisePropertyChanged("SelectedAxmModule ");
}
}
}
}
Register it in your view Code behind/ viewmodel
AxmModule.ValueChanged+= AxmModule_ValueChanged;
public void AxmModule_ValueChanged(string value)
{
// your code
}
Hope, this will resolve your issue.
I am trying to keep my question simple and to the point.
At the moment, if I have a property that updates the underlying Model data, and it therefore needs to inform a few other properties that the source has changed, I do it like this:
public Data.MeetingInfo.Meeting Meeting
{
get { return _Meeting; }
set
{
if(value != null)
{
_Meeting = value;
if (_Meeting.IsDirty)
{
_Model.Serialize();
_Meeting.MarkClean();
OnPropertyChanged("Meeting");
OnPropertyChanged("BibleReadingMain");
OnPropertyChanged("BibleReadingClass1");
OnPropertyChanged("BibleReadingClass2");
}
}
}
}
private Data.MeetingInfo.Meeting _Meeting;
As you can see, I added several different OnPropertyChanged method calls. Is this an acceptable way to do it? Or, can the specific properties in the Model inform the View that some of it's source has changed?
I have read about implementing the same OnPropertyChanged features in the Model classes. Thus the XAML will pick it up. But I thought those two parts of the MWWV we not supposed ot know about each other.
The thing is, the other 3 are in disabled controls, but they can be updated from two places on the window. So I don't think I can have two update source triggers can I?
Thank you.
Second attempt at explainign things:
ObservableCollection of Meeting objects. Bound to a ComboBox:
<ComboBox x:Name="comboMeetingWeek" ItemsSource="{Binding Meetings}"
SelectedItem="{Binding Meeting, UpdateSourceTrigger=PropertyChanged}" />
The Meeting object contains several properties. We bind controls on the window with these properties. Example:
<ComboBox x:Name="comboNotes" IsEditable="True"
DataContext="{Binding Meeting}"
Text="{Binding Note, UpdateSourceTrigger=LostFocus}"
ItemsSource="{StaticResource Notes}"/>
I do this for the majority of the controls. So the Meeting property in the view model is kept up to date and then when you select a different meeting it commits it to the model data and displays the new meeting (as previously described).
But, in some places on the window, I have some disabled text boxes. These are associated with properties nested inside the Meeting object. For example:
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
DataContext="{Binding TFGW.BibleReadingItem.Main}"
Text="{Binding DataContext.BibleReadingMain, ElementName=oclmEditor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"/>
The parent TabItem already has it's DataContext set to {Binding Meeting}. What we need to display in the text box is:
Meeting (current context).TFGW.BibleReadingItem.Main.Name
This is why I had to do it has I did. For the above text box, this is what I want to allow to happen:
It should display the content of Meeting.TFGW.BibleReadingItem.Main.Name (Meeting already being a bound property).
As you select a different meeting from the dates combo, this text box should update.
If the user selects a name from the DataGrid and the ActiveAstudentAssignmentType combo is set to StudentAssignmentType::BibleReadingMain then I also want to update the text box.
I think what I am getting confused about is when I am supposed to derive my classes from INotifyPropertyChanged. My Model data is the Meeting objects with it's own data. Should all of these be inheriting from INotifyPropertyChanged and raising OnPropertyChanged? At the moment I do not have that implemented anywhere. I tell a lie, the only place I implemented it was for the view model itself:
public class OCLMEditorViewModel : INotifyPropertyChanged
So that is why I had to do it the way I did.
Any clearer?
Based on all the comments and further reasearch ....
One of the answers stated:
Viewmodel is created and wraps model
Viewmodel subscribes to model's PropertyChanged event
Viewmodel is set as view's DataContext, properties are bound etc
View triggers action on viewmodel
Viewmodel calls method on model
Model updates itself
Viewmodel handles model's PropertyChanged and raises its own PropertyChanged in response
View reflects the changes in its bindings, closing the feedback loop
I also read a bit of this (which confused me somewhat) where it stated:
The Model notifies the ViewModel if the data in the underlying data store has changed.
So, the first thing I did was change my Meeting object to derive from INotifyPropertyChanged. In addition, I added new properties for gaining access to deeper data in the Meeting model. Example (stripped down):
public class Meeting : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
#region Bible Reading Name Properties
[XmlIgnore]
public string BibleReadingMainName
{
get { return _TFGW.BibleReadingItem.Main.Name; }
set
{
_TFGW.BibleReadingItem.Main.Name = value;
OnPropertyChanged("BibleReadingMainName");
}
}
[XmlIgnore]
public string BibleReadingClass1Name
{
get { return _TFGW.BibleReadingItem.Class1.Name; }
set
{
_TFGW.BibleReadingItem.Class1.Name = value;
OnPropertyChanged("BibleReadingClass1Name");
}
}
[XmlIgnore]
public string BibleReadingClass2Name
{
get { return _TFGW.BibleReadingItem.Class2.Name; }
set
{
_TFGW.BibleReadingItem.Class2.Name = value;
OnPropertyChanged("BibleReadingClass2Name");
}
}
#endregion
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
In my ViewModel I set it as a listener for PropertyChanged:
_Meeting.PropertyChanged += Meeting_PropertyChanged;
At this point in time, the handler just relays the property that was changed:
private void Meeting_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged(e.PropertyName);
}
In my XAML, I adjust my TextBox to work with the new property, and I remove the DataContext reference. So I now have:
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
Text="{Binding BibleReadingMainName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
ON the right, where I have the DataGrid, when we click a row and the SelectedStudentItem is updated, we can now do:
private Student _SelectedStudentItem;
public Student SelectedStudentItem
{
get
{
return _SelectedStudentItem;
}
set
{
// We need to remove this item from the previous student history
if (_SelectedStudentItem != null)
_SelectedStudentItem.History.Remove(Meeting.DateMeeting);
_SelectedStudentItem = value;
if (_SelectedStudentItem == null)
return;
_EditStudentButtonClickCommand.RaiseCanExecuteChanged();
_DeleteStudentButtonClickCommand.RaiseCanExecuteChanged();
OnPropertyChanged("SelectedStudentItem");
if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingMain)
_Meeting.BibleReadingMainName = _SelectedStudentItem.Name;
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass1)
_Meeting.BibleReadingClass1Name = _SelectedStudentItem.Name;
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass2)
_Meeting.BibleReadingClass2Name = _SelectedStudentItem.Name;
}
Based on the current ActiveStudentAssignmentType value we can directly update the source property. Thus the TextBox will automatically know about it due to the PropertyChange listener.
Thus, the original Meeting property code now looks like this:
public Data.MeetingInfo.Meeting Meeting
{
get { return _Meeting; }
set
{
// Has the existing meeting object changed at all?
if(_Meeting != null && _Meeting.IsDirty)
{
// Yes, so save it
_Model.Serialize();
_Meeting.MarkClean();
}
// Now we can update to new value
if (value != null)
{
_Meeting = value;
OnPropertyChanged("Meeting");
}
}
}
private Data.MeetingInfo.Meeting _Meeting;
All of those extra OnPropertyChanged calls are now obsolete!
The thing I was missing was implementing Notification from the Model to the ViewModel. And then the ViewModel informing the View.
In my WPF application I have a combo box. It binds data by MVVM design pattern. It works fine.
In the XAML
<ComboBox x:Name="comboBox1" ItemsSource="{Binding ValetType}" DisplayMemberPath="Name"
SelectedItem="{Binding SelectedItemValet, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True"/>
In the ViewModel
valetTypes = GetValueForValet();
public ObservableCollection<Valet> ValetType
{
get { return valetTypes; }
set { valetTypes = value; NotifyPropertyChanged("ValetType"); }
}
public Valet SelectedItemValet
{
get { return selectedItemValet;}
set { selectedItemValet = value; NotifyPropertyChanged("SelectedItemValet"); }
}
One of the problems I face is that when I do not change the combo box value it binds null, otherwise it binds. I set IsSynchronizedWithCurrentItem as true it tells a Selector should keep the SelectedItem synchronized with the current item. But would not work. How could I do this? I am new to WPF MVVM.
You´ve got one problem in your code:
public ValetServices SelectedItemValet
{
get { return selectedItemValet;}
set { selectedItemValet = value; NotifyPropertyChanged("SelectedItemValet"); }
}
ValetType is Type Valet, but SelectedItemValet is Type ValetServices. They should match. After changing that, to select the first item for example, do this:
valetTypes = GetValueForValet();
this.SelectedItemValet=ValetType.FirstOrDefault();
The easy solution would be to provide a default value to your SelectedItemValet.
In some method of your ViewModel, that is called when you ViewModel is initialized (typically the constructor) you can set SelectedItemValet to the value you want
I am using MVVM pattern and I have LongListSelector in my page but I am not sure how to do these bindings:
I want in each row a button which do something with object in that row. I have this prepare in ViewModel:
private RelayCommand<Module> goToTrackingPageCommand;
public RelayCommand<Module> GoToTrackingPageCommand
{
get
{
return goToTrackingPageCommand
?? (goToTrackingPageCommand = new RelayCommand<Module>(
NavigateToTrackingPage));
}
}
private void NavigateToTrackingPage(Module module)
{
App.Current.SelectedModule = module;
navigationService.NavigateTo(new Uri("/Views/ModuleTrackingPage.xaml"), UriKind.Relative);
}
And I am trying to bind it like this:
<Button x:Name="ShowButton" Content="Show"
Command="{Binding GoToTrackingPageCommand}"
CommandParameter="{Binding}"/>
It's not working because button is in datatemplate and when there is binding it goes to selected Module object but not to ViewModel. So my first question is how can I fix it?
Second one is little complicated I guess but hope both get easy solution. I want have in each row ToggleSwitch too and when value is changed I want to call http request. I have this in datatemplate:
<toolkit:ToggleSwitch x:Name="LockSwitch" IsChecked="{Binding IsLock}" />
I could change binding to TwoWay but I change value in object and I want to call method in ViewModel with Module argument. So have can I change this binding? Should I someway call method in ViewModel from my object? Or should I somehow tell ViewModel that this object has changed this value? Or should I bind Checked and Unchecked event?
Regarding the Buttons:
You can access the "parent datacontext" with an elementName binding and set the command parameter:
<Button Command="{Binding DataContext.GoToXyCommand, ElementName=LayoutRoot}" CommandParameter="{Binding}" />
Regarding your second question:
First, I would check if a toggle-button is the right solution, if changing the value triggers a process with a possibly longer duration.
An example where WP does this is enabling/disabling Air-Plane Mode.
I would do it the same way:
Bind to the property via TwoWayBinding
When the property is changed, start the updating process, disable the toggle button and show a progress indicator.
EDIT: Here is and example from a ViewModel I recently used.
public bool IsUpdatingPushEnabled
{
get { return _isUpdatingPushEnabled; }
set { SetProperty(ref _isUpdatingPushEnabled, value); }
}
public bool IsPushEnabled
{
get { return _isPushEnabled; }
set
{
if (!IsUpdatingPushEnabled)
{
SetProperty(ref _isPushEnabled, value);
var t = SetPushAsync();
}
}
}
private async Task SetPushAsync()
{
IsUpdatingPushEnabled = true;
try
{
var result = await _settingService.EnablePushAsync(IsPushEnabled);
SetProperty(ref _isPushEnabled, result, "IsPushEnabled");
}
catch
{
//...
}
IsUpdatingPushEnabled = false;
}
I have the below UI, which allows you to select a team in the left, then edit properties of the selected team on the right. Here's an example scenario demonstrating the issue:
Select Dragon team
Rename to Smaug, press save.
"Dragon" in selection panel on left doesn't update to "Smaug". However, if I select another team, the reselect "Dragon", the textbox on the right side still (correctly) shows "Smaug". I'm pretty sure this means that the databound collection is correctly being updated.
Close the Settings window, then reopen it.
Left panel now (correctly) shows "Smaug".
The list of teams is being stored as an observable collection:
public class TeamList : ObservableCollection<Team>
{
public TeamList() : base() { }
}
Team list on the left is being populated/bound:
SettingsWindow.xaml
<ListView ItemsSource="{Binding}" Grid.Column="0" Grid.Row="1" DisplayMemberPath="name"
SelectionChanged="ListTeamSelected" SelectionMode="Single">
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="{Binding color}" />
</Style>
</ListView.ItemContainerStyle>-->
</ListView>
SettingsWindow.xaml.cs
public Team selectedTeam { get; set; }
public SettingsWindow()
{
teams = TeamManager.Instance().teamList;
this.DataContext = this.teams;
if (!Application.Current.Resources.Contains("selectedTeam"))
Application.Current.Resources.Add("selectedTeam", selectedTeam);
InitializeComponent();
}
Data on the right is being populated and saved:
SettingsWindow.xaml.cs
private void ClickSaveData(object sender, RoutedEventArgs e)
{
selectedTeam.name = TeamName.Text;
selectedTeam.color = PrimaryColorPicker.SelectedColor;
selectedTeam.secondaryColor = SecondaryColorPicker.SelectedColor;
saved = true;
}
private void ListTeamSelected(object sender, RoutedEventArgs e)
{
selectedTeam = (Team)(sender as ListView).SelectedItems[0];
TeamInfo.Visibility = Visibility.Visible;
TeamName.Text = selectedTeam.name;
PrimaryColorPicker.SelectedColor = selectedTeam.color;
SecondaryColorPicker.SelectedColor = selectedTeam.secondaryColor;
}
Twofold question:
Am I doing anything wrong with my databinding that's causing this issue? (I'm new at WPF)
If not, is there a way for me to force the UI to update the list on the left? (this seems vaguely hacky to me)
Thank you in advance for any assistance!
Your properties need to implement the INotifyPropertyChanged interface for databinding to work properly.
For example (from http://wpftutorial.net/INotifyPropertyChanged.html)
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged("Name");
}
}
private void PropertyChanged(string prop)
{
if( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(prop);
}
}
I would highly recommend the MVVM Light Toolkit for any MVVM work though (https://mvvmlight.codeplex.com/). If you use the MVVM Light Toolkit then you can inherit from ViewModelBase and then implement your properties like this
private string _orgId;
public string OrgId
{
get { return _orgId; }
set { _orgId = value; RaisePropertyChanged("OrgId"); }
}
I didn't know to have the my Team class implement INotifyPropertyChanged. This link was very helpful and straightforward. A couple things to note for others who have never worked with data-binding before:
You need getters and setters for all the properties you want to notify on in order to throw the event.
You need to use private variables for holding the data itself, or the setter will trigger itself, throwing you into a stack overflow.
The parameter for the event is the public name of the property that was changed, not the private name nor the value.
Thanks to #ReggaeGuitar for the answer!