I have an db generated entity:
public partial class UserMobileDevice
{
public string DeviceID { get; set; }
public string DeviceType { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
Now I would like to hook up a ComboBox that selects a User and updates the UserID and User object on a record.
So this is my xaml:
<ComboBox Name="cboDefaultUser"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Users}"
DisplayMemberPath="Username"
SelectedValuePath="UserID"
SelectedValue="{Binding TheEntity.UserID,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True,
NotifyOnValidationError=True,
Mode=TwoWay}" />
Which updates the UserID on the local entity just fine, however what about the User object? How do I go about assigning that at the same time?
(Using MVVM as well)
[Addition]
As you can see from the SelectedValue binding, my ViewModel has the entity in a property called TheEntity. So there is no UserID{get;set;} property actually in the ViewModel, which I can also use to also set the User object. So how could I accomplish this?
The ComboBox will only set one property for you. If you want to set both the User property and the UserId property, you could bind to a custom property that you define in another partial class definition of the same type and that does this for you:
public partial class UserMobileDevice
{
public User UiUser
{
get { return User; }
set
{
User = value;
if (value != null)
UserId = value.UserId;
}
}
}
You then bind the SelectedItem property of the ComboBox to this one:
<ComboBox Name="cboDefaultUser"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Users}"
DisplayMemberPath="Username"
SelectedItem="{Binding TheEntity.UiUser,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True,
NotifyOnValidationError=True}" />
Trying to use auto-generated types exactly "as-is" is seldom very useful.
Related
I have a list of Roles that I want to have selectable in a DataGrid cell ComboBox.
I have the ObservableCollection Roles for the ComboBox being populated in the ViewModel.
For some reason, nothing shows up in the ComboBox though. What's the proper way of attaching this collection of objects?
Any comments or suggestions would be helpful.
PersonModel:
public int SelectedRole { get; set; }
RoleModel:
public int Id { get; set; }
public int Role { get; set; }
public string Description { get; set; }
public string RoleInfo
{
get
{
return $"{Role} - {Description}";
}
}
ViewModel:
private ObservableCollection<PersonModel> _people;
public ObservableCollection<PersonModel> People
{
get { return _people; }
set
{
_people = value;
NotifyOfPropertyChange(() => People);
}
}
public ObservableCollection<RoleModel> _roles;
public ObservableCollection<RoleModel> Roles
{
get
{
return _roles;
}
set
{
_roles = value;
NotifyOfPropertyChange(() => Roles);
}
}
public PersonViewModel(IEventAggregator events, IWindowManager windowmanager)
{
_events = events;
_windowManager = windowmanager;
sql = "SELECT * FROM People";
People = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
sql = "SELECT * FROM Roles";
Roles = SqliteConnector.LoadData<PersonModel>(sql, new Dictionary<string, object>());
}
View:
<DataGrid ItemsSource="{Binding Path=People}"
AutoGenerateColumns="False"
CanUserDeleteRows="True"
CanUserReorderColumns="True"
CanUserAddRows="True"
AlternatingRowBackground="#dfdfdf"
cm:Message.Attach="[Event RowEditEnding] = [Action SaveOrUpdate()]">
<DataGridTemplateColumn Header="Type">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Role}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="RoleInfo"
ItemsSource="{Binding Path=Roles}"
SelectedValue="{Binding Path=SelectedRole, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Role" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid>
The DataContext of the "row" elements of the DataGrid is the corresponding item of the ItemsSource collection, i.e. a PersonModel instance - which does of course not have a Roles property. You should have observed a corresponding data binding error message in the Output Window in Visual Studio.
In order to bind to the Roles property of the parent view model, use an expression like this:
ItemsSource="{Binding DataContext.Roles,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
The SelectedValue Binding could simply be like shown below, because PropertyChanged is already the default UpdateSourceTrigger for that property.
SelectedValue="{Binding Path=SelectedRole}"
When you run your program, check the output window to see exactly what kind of error is coming up for this control when it loads. This will give you a better idea of the exception type being thrown. My guess is that the ItemsSource of the ComboBox is looking for the ObservableCollection Roles in your Person Model, and is throwing an exception since Roles is in only in your ViewModel. Try moving this collection to the Person Model and assign it's initial value via the Person Model constructor.
I have class DimensionType, it has properties Name, Id, etc. I constructed Property in my ViewModel = "dimStyleId" to retrieve selected form ComboBox. I am getting null in this property although I checked it in TextBlock and get it.
<!--Dimension Type Combobox-->
<ComboBox x:Name="DimensionType"
ItemsSource="{Binding dimTypes , Mode=TwoWay}"
SelectedValue="{Binding dimStyleId , Mode=TwoWay}"
SelectedValuePath="DimensionType"
DisplayMemberPath="Name"
Padding="3" />
and here is my VM Class
public class GridsDimViewModel : INotifyPropertyChanged
{
public ElementId dimensionType;
private ElementId _dimStyleId { get; set; }
public ElementId dimStyleId
{
get
{
return _dimStyleId;
}
set
{
if (_dimStyleId != value)
{
_dimStyleId = value;
NotifyPropertyChanged(nameof(dimStyleId));
}
}
}
}
and here is my check textbox which gets the id in it
<TextBlock Text="{Binding dimStyleId}"
Padding="3" />
Swap
SelectedValuePath="DimensionType"
to
SelectedValuePath="Id"
I try to display values in my ComboBox using Binding. But I have no idea why isn't it working:
<ComboBox Width="476" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,10,0,0" ItemsSource="{Binding Maps.Name}"></ComboBox>
Here is my C#:
public class Map
{
public string Name { get; set; }
public string ImagePath { get; set; }
}
And main:
class MainWindowViewModel : BindableBase
{
public ObservableCollection<Map> Maps { get; set; }
public MainWindowViewModel()
{
Maps = mainWindowModel.LoadMapFiles(); //deserializes maps, i checked it, LoadMapFiles() works
}
}
What should I write in ComboBox ItemSource if I want it to display every Map.Name?
The expression Maps.Name is not a valid Binding Path, because Name is not a property of the ObservableCollection<Map> in Maps.
Bind the ItemsSource property to the collection property, and set the displayed property by DisplayMemberPath:
<ComboBox ItemsSource="{Binding Maps}" DisplayMemberPath="Name" ... />
Also make sure that the Maps property setter fires a change notification, or make the property read-only:
public ObservableCollection<Map> Maps { get; }
I have a model, and I don't want it implement INotifyPropertyChanged:
public class PeopleModel
{
public string name { get; set; }
public string company { get; set; }
}
And now I have a ViewModel:
public class DetailViewModel : CustomViewModelBase
{
private PeopleModel _person;
public PeopleModel person
{
get
{
return _person;
}
set
{
_person=value;
OnPropertyChanged();
}
}
public ICommand customOnUpdateCommand { get; set; }
}
Now, using WPF and only using XAML, I bind a textbox content to the name (The whole window is already bind to the ViewModel):
<StackPanel
DataContext="{Binding Path=person}">
<DockPanel>
<Label Content="name" Target="{Binding ElementName=person_TextBox}" />
<TextBox x:Name="person_TextBox"
Text="{Binding Path=name, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</DockPanel>
</StackPanel>
Now, I want to do the following: when I change the name property in this person object, the person object should raise a "PropertyChanged" in the ViewModel. Or alternative, execute a custom command, or execute a method(which would be much better).
I already tried data triggers, System.Windows.Interactivity, or using RelativeSource, but they don't work, maybe I didn't use them correctly.
How do I achieve this correctly?
Note: I already confirmed that when the textbox changes, the name in the object will change correctly. So the binding process is correct.
The scenario I'm working with is editing Roles and Permissions. In a list box I want to list all defined Permissions and check the Permissions the selected Role has been assigned. The Role selection occurs in a separate list.
I have a simple view that contains a list box that displays all permissions:
<ListBox
...
ItemsSource="{Binding AllPermissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}"
IsChecked="???"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ItemsSource is one set of Permissions and the selected role's Permissions is a different set. How would I bind the IsChecked value to the intersection of the sets (i.e., if the Permission in the ListBox is also in the selected role's Permissions then the box should be checked)?
I'm guessing your Item source of AllPermissions is a collection of Permission objects. So just make sure that in addition to the DisplayName that it also has something on there that determines whether the role has the permission:
public class Permission : ViewModelBase
{
private string displayName;
private bool roleHasPermission;
public string DisplayName
{
get
{
return this.displayName;
}
set
{
this.displayName = value;
this.RaisePropertyChanged(() => this.DisplayName);
}
}
public bool RoleHasPermission
{
get
{
return this.roleHasPermission;
}
set
{
this.roleHasPermission = value;
this.RaisePropertyChanged(() => this.RoleHasPermission);
}
}
}
so then Bind IsChecked to RoleHasPermission.
Now I'm guessing at the moment that you are loading the available permissions from somewhere and they are currently ignorant of if the role has thepermission, so when you are loading the AllPermissions, calculate whether the rolehas the permission.
I have assumed you have inherited from a base class that has a RaisePropertyChanged event on it to notify the view when the value has been updated. (such as provided for you if you use mvvm light or other frameworks, or you can write your own) Also if you want to be able to edit the permission by checking/unchecking the check box, then remember to set the binding Mode=TwoWay ie:
<ListBox
...
ItemsSource="{Binding AllPermissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding DisplayName}"
IsChecked="{Binding RoleHasPermission, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
I would try using the Converter property of the binding for IsChecked. The ConverterParameter for the binding would be set to the list of permissions for the selected role.
I don't know how familiar you are with ValueConverters, but I could write some code to illustrate if that would help. ValueConverter examples are easy to find on the internet. The first time I ever implemented a ValueConverter, I followed this example from David Anson's blog.
I would write a ViewModel for your Role that has a collection of Permissions.
public class PermissionViewModel : ViewModelBase
{
public string Name { get; set; }
public bool HasPermission { get; set; }
}
public class RoleViewModel : ViewModelBase
{
public string Name { get; set; }
public ObservableCollection<PermissionViewModel> PermissionViewModels { get; set; }
}
<ListBox
...
ItemsSource="{Binding SelectedRoleViewModel.PermissionViewModels}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
IsChecked="{Binding HasPermission, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>