I want to be able to show a distance in both metric and imperial units.However changing the unit system through a ComboBox isn't changing the format of the label.
Some details:
1) The data context is working fine
2) I get "4.00 km" when I start the program but changing the Combobox's value has no effect.
3) ObservableObject has OnPropertyChanged() and it's also working fine everywhere except here.
WPF UI
<ComboBox ItemsSource="{Binding UnitSystems}" SelectedValue="{Binding Units}"/>
<Label Content="{Binding Distance}" ContentStringFormat="{Binding DistanceFormat}"/>
C# View Model
public class ViewModel : ObservableObject
{
private double distance = 4;
public double Distance
{
get
{
return distance;
}
set
{
distance = value;
OnPropertyChanged("Distance");
}
}
private UnitSystem units;
public List<UnitSystem> UnitSystems
{
get
{
return new List<UnitSystem>((UnitSystem[])Enum.GetValues(typeof(UnitSystem)));
}
}
public UnitSystem Units
{
get
{
return units;
}
set
{
units = value;
OnPropertyChanged("Units");
OnPropertyChanged("DistanceFormat");
OnPropertyChanged("Distance");
}
}
public string DistanceFormat
{
get
{
if (Units == UnitSystem.Metric)
return "0.00 km";
else
return "0.00 mi";
}
}
}
public enum UnitSystem
{
Metric,
Imperial
}
Edit: The multibinding solution below has the same problem and it's not because of the format strings. Using f3 and f4, I start with "4.000" and it doesn't change to "4.0000".
<ComboBox ItemsSource="{Binding UnitSystems}" SelectedValue="{Binding Units}"/>
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource FormatConverter}">
<Binding Path="Distance"/>
<Binding Path="DistanceFormat"/>
</MultiBinding>
</Label.Content>
</Label>
Edit 2 (SOLVED): The problem was that ObservableObject didn't implement INotifyPropertyChanged and surprisingly worked fine until this point.
Bind Units to SelectedItem instead of SelectedValue:
<ComboBox
ItemsSource="{Binding UnitSystems}"
SelectedItem="{Binding Units}"
/>
SelectedValue is used when you use SelectedValuePath="SomePropertyName" to specify a property of the selected item that you care about. But enums have no properties, so just grab the item itself.
Here's my stand-in for ObservableObject:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string prop = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Nothing clever. I create those with a snippet.
Related
I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.
I have a TextBlock in a ComboBox in a C# WPF project bound to a list of 'Envelope' items, which have a string 'Name' and a double 'Weight' property, the former of which I would like to see displayed in the TextBlock.
When I run my program, the ComboBox appears without any text in it. It properly has three unlabeled items in it, and if I view the ItemsSource or SelectedItem of the ComboBox they show the appropriate values, and other code which interacts with the SelectedItem of the ComboBox behaves properly. The only thing that does not work is that the TextBlock contains no text. If I replace the "{Binding Name}" with "au ghdfjlnvgmumar" then the appropriate garbled characters appear in the ComboBox, so it is definitely a problem with the binding. What is the problem, and how can I get around it?
Relevant code:
xaml:
<ComboBox Name="EnvelopeList" HorizontalAlignment="Center" Width="200" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
C#:
//main window code
public MainWindow()
{
InitializeComponent();
envelopes = new List<Envelope>();
envelopes.Add(new Envelope("TEST", 0));
envelopes.Add(new Envelope("HI", 10));
EnvelopeList.ItemsSource = envelopes;
}
//Envelope class
class Envelope
{
public string Name;
public double Weight;
public Envelope()
{
Name = "[None]";
Weight = 0;
}
public Envelope(string n, double w)
{
Name = n;
Weight = w;
}
public override string ToString()
{
return Name;
}
}
When DataBinding, you can only bind to Properties. Also, you need to update your properties with a PropertyChangedEvent. Otherwise, if you change your property after the initial binding it won't update the UI.
You need to use on property changed and a property
public class Envelope: ModelBase
{
private string _name;
public string Name
{
get { return _name; }
set { _name= value; OnPropertyChanged("Name"); }
}
}
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
}
Finally, I notice that you're setting ItemsSource directly. Instead, you want to set your View's DataContext property and then bind to your ItemsSource
Here is a MSDN article on DataBinding that will teach you how to do it properly.
Name is a field, you can only bind to properties.
I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.
Cant get any data to work with databinding, I have the INotify event, I have the binding on the xaml objects, but nothing shows up, if I change the content on the lables to "something" it works, but nothing shows on load or on click on my button
My Xaml view
<Grid>
<StackPanel Name="stackpanel">
<Label Content="{Binding Name}" />
<Label Content="{Binding Length}" />
<Label Content="{Binding Rating}" />
<Button Content="Change text" Click="ButtonClick" />
</StackPanel>
</Grid>
Its codebehind
public partial class Movie
{
readonly MovieViewModel _movieViewModel;
public Movie()
{
InitializeComponent();
_movieViewModel = new MovieViewModel { Movie = { Name = "The Dark Knight", Length = 180, Rating = 88 } };
stackpanel.DataContext = _movieViewModel;
}
private void ButtonClick(object sender, RoutedEventArgs e)
{
_movieViewModel.Movie.Name = "bad movie";
}
}
The View Model
class MovieViewModel
{
public MovieViewModel() : this(new Movie())
{
}
public MovieViewModel(Movie movie)
{
Movie = movie;
}
public Movie Movie { get; set; }
}
The Model
class Movie : INotifyPropertyChanged
{
public Movie()
{}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private int _length;
public int Length
{
get { return _length; }
set
{
_length = value;
NotifyPropertyChanged("Length");
}
}
private int _rating;
public int Rating
{
get { return _rating; }
set
{
if (_rating == value) return;
_rating = value;
NotifyPropertyChanged("_Rating");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You have your bindings set incorrectly, that's the reason nothing is being shown.
Just take a closer look at your ViewModel and than on the bindings. You try to bind to property named Name but your MovieViewModel does not expose any property with that name. I'm pretty sure binding errors were reported to you (look through messages in Output window).
To make it work, you need either expose properties in your ViewModel to match the ones you try to bind to (bad), or change bindings in your xaml to have correct path:
<Label Content="{Binding Movie.Name}" />
<Label Content="{Binding Movie.Length}" />
<Label Content="{Binding Movie.Rating}" />
This should get you going.
Additionally - you may want to implement INotifyPropertyChanged also on your MovieViewModel class if you plan to change Movie object that is assigned to Movie property. As long as you will only change properties of Movie object already assigned to MovieViewModel everything will be ok, but if you would try to change actual object assigned to this property, no changes notifications will be generated and your UI will stop working correctly.
Moreover - I noticed that you made your NotifyPorpertyChanged method public - I wouldn't advise this as anyone can now trigger this event. Normal approach is to make such methods private or protected, depending if you want to provide way to trigger event from inheriting classes (which is very likely in case of PropertyChanged event).
I think you have one typing mistake
NotifyPropertyChanged("_Rating");
Should be
NotifyPropertyChanged("Rating");
Rather than using Label, I would suggest you to use Texblock. Try the following code
_movieViewModel = new MovieViewModel
{ Movie = { Name = "The Dark Knight", Length = 180, Rating = 88 } };
this.DataContext = _movieViewModel;
and
Textblock like following
<StackPanel Name="stackpanel">
<TextBlock Name="textBlock1" Text="{Binding Path=Name}"/>
<TextBlock Name="textBlock2" Text="{Binding Path=Length}"/>
<Button Content="Change text" Click="ButtonClick" />
</StackPanel>
using Entity Framework (C#) I have a User class which has ONE:MANY mapping to the UserRight class (simply, user has a set of rights). Each right is identified by a string. And now, because the maximum number of possible rights is finite (<10) I'd like to have 10 CheckBoxes and edit the subset of rights for a given user manually.
What is the nice way to do it?
James
Create a RightViewModel class to contain user rights:
public class RightViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
Change("Name");
}
}
private bool _hasRight;
public bool HasRight
{
get { return _hasRight; }
set
{
_hasRight = value;
Change("HasRight");
}
}
public void Change(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Create a similar class for your user, containing a member Rightsof type ObservableCollection<RightViewModel>.
In you XAML, use an ItemsControl:
<ItemsControl ItemsSource="{Binding Rights}"
ItemTemplate="{StaticResource RightTemplate}"/>
And a template definition:
<DataTemplate x:Key="RightTemplate">
<CheckBox Content="{Binding Name}" IsChecked="{Binding HasRight, Mode=TwoWay}"/>
</DataTemplate>
Mode=TwoWay makes the binding update your RightViewModel instance.
Define the ItemsControl's ItemsPanel if you need to display your checkboxes with a different layout.
Finally set your user as the DataContext of your container.