I have a model with properties:
/// <summary>
/// List of available map modes
/// </summary>
public Array MapModes { get; private set; }
/// <summary>
/// The current cartographic mode of the map
/// </summary>
public MapCartographicMode MapMode
{
get { return _mapMode; }
set
{
if (value == _mapMode) return;
_mapMode = value;
OnPropertyChanged();
}
}
/// <summary>
/// List of available map color modes
/// </summary>
public Array MapColorModes { get; private set; }
//The current color mode of the map
public MapColorMode MapColorMode
{
get { return _mapColorMode; }
set
{
if (value == _mapColorMode) return;
_mapColorMode = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
And My XAML looks like this:
<maps:Map x:Name="MainMap"
Height="{Binding MapHeight}"
ColorMode="{Binding MapColorMode, Converter={StaticResource MapTestConverter}}">
The properties are updated on another page.
<toolkit:ListPicker Header="Map mode"
ItemsSource="{Binding MapModes}"
SelectedItem="{Binding Path=MapMode, Mode=TwoWay}"/>
<toolkit:ListPicker Header="Map color mode"
ItemsSource="{Binding MapColorModes}"
SelectedItem="{Binding Path=MapColorMode, Mode=TwoWay}"/>
Now the binding of the ListPickers works fine the value in the model always represents what was last picked here.
The Map binding also works, it gets the initial value and also updates the first time I change a property.
BUT that's it. after the first property change it refuses to update. (The dummy IValueConverter isn't called).
The model still nicely raises Property changed events, and the property has the correct value in the model (manually assigning it for example at page load works flawlessly)
Since it seems the Binding is getting "broken" I tried recreating it each time the property was updated
Binding b = new Binding("MapMode");
BindingOperations.SetBinding(MainMap, Map.CartographicModeProperty, b);
This works. I am beginning to think there is a bug or something in the wp8 map implementation. (Or I may just miss something completely obvious^^)
I had the same.
The solution is to provide Mode=TwoWay to the bindings. I have no idea why that works, but it seems.
On this blog I have seen exactly this solution:
http://dotnetbyexample.blogspot.ch/2012/10/introducing-new-windows-8-map-control.html
Related
I want to display the style of a control in a template depending on an enum in the used class. I tried to use this to use the enum in XAML and this to create a trigger. The problem is that I cannot use x:Static in UWP and the trigger is never fired. My workaround does not work either.
My class:
//Namespace Enums
public enum ConnectionState
{
Open,
Closed,
Connecting,
Broken
}
//Namespace Models
public class DatabaseConnection : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ConnectionState _connectionState = ConnectionState.Broken;
public ConnectionState ConnState
{
get => _connectionState;
set
{
if (value != _connectionState)
{
_connectionState = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ConnStateInt));
OnPropertyChanged(nameof(InfoBadgeStyle));
}
}
}
public int ConnStateInt => (int)ConnState;
public Style InfoBadgeStyle
{
get
{
return ConnState switch
{
ConnectionState.Open => (Style)Application.Current.Resources["SuccessIconInfoBadgeStyle"],
ConnectionState.Connecting => (Style)Application.Current.Resources["AttentionIconInfoBadgeStyle"],
ConnectionState.Broken => (Style)Application.Current.Resources["CriticalIconInfoBadgeStyle"],
_ => (Style)Application.Current.Resources["InformationalIconInfoBadgeStyle"],
};
}
}
}
My template:
<Page.Resources>
<DataTemplate x:Key="ConnectionTemplate" x:DataType="models:DatabaseConnection">
<muxc:InfoBadge Style="{x:Bind InfoBadgeStyle}"/>
</DataTemplate>
</Page.Resources>
How can I update the style with a trigger in UWP?
It's unclear why you have both an InfoBadgeStyle property and triggers in XAML but if you want your ChangePropertyActions to be able to set the Style property, you should not set the local Style property like this as it will take precedence:
<muxc:InfoBadge ... Style="{x:Bind InfoBadgeStyle}">
So either remove the InfoBadgeStyle property or remove the triggers.
Also note that x:Bind binds OneTime by default so if you want to react to change notifications you should set the Mode to OneWay: Style="{x:Bind InfoBadgeStyle, Mode=OneWay}"
The problem was that the property was bound only once. Setting Mode to OneWay fixed the problem. Thanks to mm8 for the hint.
<muxc:InfoBadge Style="{x:Bind InfoBadgeStyle, Mode=OneWay}"/>
I'm having an enormous amount of trouble with ComboBoxes in a data grid.
And really would like some help, i think i've gotten confused by the amount of research and things i've tried. This really should be simple so i must be missing something.
SIMPLIFIED PROBLEM
I use a CollectionViewSource in xaml, the C# sets the source of that CollectionViewSource to an ObservableCollection in a class that is the Page's datacontext.
Adding items to the collection does not update the DataGridComboBox column containing that displays the view source.
See below the line for more detail
OVERVIEW
I have a WPF Page with a datagrid on it.
The page has its data context set to a view model.
The viewModel contains two observable collections. One for Equips and One for Locations.
Each Equip has a Location.
These are populated from a code first EF database but i believe that this problem is above that level.
The datagrid is one row per Equip. The Location column needs to be a selectable combobox that allows the user to change Location.
The only way i could get the location combobox to populate at all is by binding it to a separate collection view source.
PROBLEM
It seems that if the Page loaded event occurs prior to the ViewModel populating the ObservableCollection then the locationVwSrc will be empty and the property changed event doesn't get this to change.
IMPLEMENTATION SHORT VERSION
Page has a collection viewSource defined in the xaml.
Loaded="Page_Loaded"
Title="EquipRegPage">
<Page.Resources>
<CollectionViewSource x:Key="locationsVwSrc"/>
</Page.Resources>
The datagrid is defined with xaml.
<DataGrid x:Name="equipsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Margin="10,10,-118,59"
ItemsSource="{Binding Equips}" EnableRowVirtualization="True" AutoGenerateColumns="False">
The combobox column defined in xaml
<DataGridComboBoxColumn x:Name="locationColumn" Width="Auto" MaxWidth="200" Header="Location"
ItemsSource="{Binding Source={StaticResource locationsVwSrc}, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
SelectedValueBinding="{Binding Location}"
The page context set to the view model
public partial class EquipRegPage : Page
{
EquipRegVm viewModel = new EquipRegVm();
public EquipRegPage()
{
InitializeComponent();
this.DataContext = viewModel;
}
Loaded event setting the context
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Locations View Source
System.Windows.Data.CollectionViewSource locationViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
locationViewSource.Source = viewModel.Locations;
// Above does not work if the viewmodel populates these after this call, only works if its populated prior.
//TODO inotifypropertychanged not correct? This occurs before the viewmodels loads, and doesn't display.
// Therefore notify property changes aren't working.
// Using this as cheat instead instead works, i beleive due to this only setting the source when its full
//viewModel.Db.Locations.Load();
//locationViewSource.Source = viewModel.Db.Locations.Local;
//locationViewSource.View.Refresh();
}
The ViewModel class and how it loads
public class EquipRegVm : DbWrap, INotifyPropertyChanged
{
/// <summary>
/// Event triggered by changes to properties. This notifys the WPF UI above which then
/// makes a binding to the UI.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify Property Changed Event Trigger
/// </summary>
/// <param name="propertyName">Name of the property changed. Must match the binding path of the XAML.</param>
void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<Equip> Equips { get; set; }
public ObservableCollection<Location> Locations { get; set; }
public EquipRegVm() : base()
{
Load();
}
/// <summary>
/// Load the data from the Model.
/// </summary>
public async void Load() //TODO async an issue?
{
// EQUIPMENT
ObservableCollection<Equip> eqList = new ObservableCollection<Equip>();
var eqs = await (from eq in Db.Equips
orderby eq.Tag
select eq).ToListAsync();
foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");
// LOCATIONS
ObservableCollection<Location> locList = new ObservableCollection<Location>();
var locs = await (from l in Db.Locations
orderby l.Name
select l).ToListAsync();
foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");
}
}
It seems you haven't been able to break the problem down into small enough problems. The question seems to be a mix of ComboBoxes in Datagrid, Asynchronously setting CollectionViewSource source, loading data from a database.
I suggest that it would be beneficial to either consider
recreating the problem (or soultion) with the minimum moving parts i.e. a XAML file and a ViewModel with pre canned data.
or decoupling your existing code. It appears that Page knows about your ViewModel explicitly (EquipRegVm viewModel = new EquipRegVm();), and you ViewModel knows explicitly about Databases and how to load itself. Oh snap, now our views are coupled to your database? Isn't that the point of patterns like MVVM so that we are not coupled?
Next I look at some the code and see some more (of what I would call) anti-patterns.
Settable collection properties
code behind for the page (all could live in the XAML)
But I think basically if you just changed your code in 3 places you should be fine.
Change 1
/*foreach(var eq in eqs)
{
eqList.Add(eq);
}
Equips = eqList;
RaisePropertyChanged("Equips");*/
foreach(var eq in eqs)
{
Equips.Add(eq);
}
Change 2
/*foreach (var l in locs)
{
locList.Add(l);
}
Locations = locList;
RaisePropertyChanged("Locations");*/
foreach (var l in locs)
{
Locations.Add(l);
}
Change 3
Either just remove the usage of the CollectionViewSource (what does it offer you?) or use binding to set the source. As you are currently manually setting the Source (i.e. locationViewSource.Source = viewModel.Locations;) you have opted out of getting that value updated when the PropertyChanged event has been raised.
So if you just delete the CollectionViewSource, then you just have to bind to the Locations property. If you decide to keep the CollectionViewSource then I would suggest deleting the page codebhind and just changing the XAML to
<CollectionViewSource x:Key="locationsVwSrc" Source="{Binding Locations}" />
Set a Binding like below :
System.Windows.Data.CollectionViewSource locationViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("locationsVwSrc")));
// locationViewSource.Source = viewModel.Locations;
Binding b = new Binding("Locations");
b.Source = viewModel;
b.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(locationViewSource, CollectionViewSource.SourceProperty, b);
This is all you need.
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.
I've a Model and I receive a property called birth in this format
birthday, birthplace
I've set this inside the Model
private string _birth;
/// <summary>
/// Birth information in format: birthday, birthplace
/// </summary>
[Column("birth")]
[JsonProperty("birth")]
public string Birth
{
get { return this._birth; }
set
{
this._birth= value;
OnPropertyChanged();
OnPropertyChanged("BirthData");
}
}
Also I've set a public modifier like this
/// <summary>
/// Split the birth data if exists
/// </summary>
public string[] BirthData
{
get { return this.Birth?.Split(','); }
}
In my XAML file I've setup everything correctly for my Model, except that I can't understand how I can use TwoWay binding on something like this. Below you will find the extract of the XAML for this particular property
<Label x:Name="BirthdayLabel" Content="Nato Il" />
<DatePicker x:Name="BirthdayDateTimePicker" SelectedDate="{Binding selectedModel.BirthData[0], Mode=TwoWay}"></DatePicker>
<Label x:Name="BirthplaceLabel" Content="Nato A"/>
<TextBox x:Name="BirthplaceTextBox" Text="{Binding selectedModel.BirthData[1], Mode=TwoWay}"/>
Of course this is not working properly because I'll ended up with selectedModel using the old information about the property even though the binding works as expected, I think that the TwoWay binding doesn't work with an Array of data like that.
I cannot change the DataSource and I've to find a method to use one textbox and one date picker and resemble together inside my ViewModel, which doesn't have much except a single method called Update() which take the current selectedModel.
ViewModel is about preparing the data for the View
So I suggest that you split and parse the data in the View Model and expose two properties for BirthDate and BirthPlace :
class Person
{
private string _birth;
public string Birth{
get { return this._birth; }
set
{
this._birth = value;
SplitBirthIntoBirthDayAndBirthPlace();
OnPropertyChanged();
}
}
private DateTime _birthday;
public DateTime Birthday{
get { return _birthday; }
set
{
_birthday = value;
ReComputeBirth();
OnPropertyChanged();
}
}
// Same for Birthplace ...
private void ReComputeBirth(){
// ... Format the data as expected ...
_birth = _birthday + ", " + _birthplace;
}
private void SplitBirthIntoBirthDayAndBirthPlace()
{
String[] values = _birth.Split(',', ' ');
// ... really make the parse here to fill _birthplace and _birthdate...
}
// ....
}
And the binding is simpler:
<DatePicker x:Name="BirthdayDateTimePicker"
SelectedDate="{Binding selectedModel.Birthday, Mode=TwoWay}"/>
<TextBox x:Name="BirthplaceTextBox"
Text="{Binding selectedModel.Birthplace, Mode=TwoWay}"/>
I have a Combobox and I want to edit a boolean value.
<dxe:ComboBoxEdit ItemsSource="{Binding EnumItemsSource}"
DisplayMember="Name"
ValueMember="Id"
IsTextEditable="False"
EditValue="{Binding TargetValue, UpdateSourceTrigger=PropertyChanged}"/>
My ViewModel:
/// <summary>
/// Contains the ItemsSource for Enums
/// </summary>
public List<EnumItemObject> EnumItemsSource
{
get { return _enumItemsSource; }
set
{
_enumItemsSource = value;
OnPropertyChanged();
}
}
public class EnumItemObject
{
public int Id {get;set;}
public string Name {get;set;}
}
And I prepare the data for the Combobox ItemsSource that:
/// <summary>
/// Sets the value to the properties for the BitTemplate view. (similar with EnumTemplate)
/// </summary>
/// <param name="propertyInfo">a boolean property</param>
private void PrepareDataForBitTemplate(PropertyInfo propertyInfo)
{
TargetValue = (int)propertyInfo.GetValue(_firstSelectedItem);
EnumItemsSource = new List<EnumItemObject>();
EnumItemsSource.Add(new EnumItemObject() { Id = 0, Name = "Nein" });
EnumItemsSource.Add(new EnumItemObject() { Id = 1, Name = "Ja" });
}
Is it the approach correct? Any solution easier?
Thanks
More natural way in WPF is to use a Value Converter. In short it's an object that manipulates how the values of a property are bound to the UI object.
In your case that means (just for example - there are certainly more approaches) creating a converter which would convert the boolean to 0 or 1 and you can then bind that property to the SelectedIndex of a ComboBox with pre-defined text Nein/Ja values (on the correct indexes) with that converter.
An unrelated thing: if you're developing your app with GUI text in another language than English, I would consider moving it to a resources file with English names for the keys - to make the code more accessible to people not speaking German for example. It may be the case you don't need it, I just felt I should mention it :)