Reputation: 91
I am working on one WPF (MVVM) Application with Caliburn Micro, I went by the standard wordings that in properties should reside in VM, In My App I've 3 textboxs and one datagrid, What I want is that whenever use selects any row the Textboxs must show the selected values (Single Selection Only !), Textboxes will show different data based on Columns. I've model also for the same structure, The problem is I've set the x:name of the control as property as per naming conventions of CM, now the selection is taken from the model so How can I set the local VM property from the Model property.
My Model :
public class PackageModel
{
public int id { get; set; }
public string sessionName { get; set; }
public int sessionInMins { get; set; }
public int sessionAmount { get; set; }
public bool isActive { get; set; }
}
My ViewModel :
private string _packName;
public string PackageName
{
get { return _packName; }
set
{
_packName = value;
NotifyOfPropertyChange(() => PackageName);
}
}
private int _amount;
public int Amount
{
get { return _amount; }
set {
_amount = value;
NotifyOfPropertyChange(() => Amount);
}
}
private int _mins;
public int Mins
{
get { return _mins; }
set {
_mins = value;
NotifyOfPropertyChange(() => Mins);
}
}
private bool _isActive;
public bool IsPackageActive
{
get { return _isActive; }
set {
_isActive = value;
NotifyOfPropertyChange(() => IsPackageActive);
}
}
private List<PackageModel> _packageList;
public List<PackageModel> PackageList
{
get { return _packageList; }
set
{
_packageList = value;
NotifyOfPropertyChange(() => PackageList);
}
}
private PackageModel _selectedPackage;
public PackageModel SelectedPackage
{
get
{
return _selectedPackage;
}
set
{
_selectedPackage = value;
NotifyOfPropertyChange(() => SelectedPackage);
}
}
}
My View :
<!--ROW 1-->
<TextBox
x:Name="PackageName"
Width="210"
Margin="5 5"
Controls:TextBoxHelper.Watermark="Package Name"
Controls:TextBoxHelper.ClearTextButton="True"
HorizontalAlignment="Left"
Grid.Row="0"
Grid.Column="0"/>
<!--ROW 2-->
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<TextBox
x:Name="Mins"
Width="100"
Margin="5 5"
Controls:TextBoxHelper.Watermark="Min(s)"
Controls:TextBoxHelper.ClearTextButton="True"/>
<TextBox Width="100"
x:Name="Amount"
Margin="5 5"
Controls:TextBoxHelper.Watermark="Amount"
Controls:TextBoxHelper.ClearTextButton="True"/>
<CheckBox
x:Name="IsPackageActive"
Margin="5 5"
Content="Is Active?"
IsChecked="{Binding SelectedPackage.isActive}"/>
</StackPanel>
<!--ROW 3-->
<DataGrid x:Name="PackageList"
SelectedItem="{Binding SelectedPackage, Mode=TwoWay}"
Grid.Row="2"
Grid.ColumnSpan="1"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserSortColumns="False"
CanUserReorderColumns="False"
CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding id}" Visibility="Hidden" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding sessionName}" Width="120" IsReadOnly="True"/>
<DataGridTextColumn Header="Min(s)" Binding="{Binding sessionInMins}" IsReadOnly="True"/>
<DataGridTextColumn Header="Amount" Binding="{Binding sessionAmount}" IsReadOnly="True"/>
<DataGridCheckBoxColumn IsReadOnly="True" Header="Is Active?" Binding="{Binding isActive}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
Please suggest if I am following any wrong standard or wrong way of setting properties.
Upvotes: 0
Views: 1978
Reputation: 169370
You could bind the Text
property to the appropriate source property explicitly (and remove the Amount
property from the view model), e.g.:
<TextBox Width="100" x:Name="Amount" Margin="5 5" Text="{Binding SelectedPackage.sessionAmount}" />
Or you could set the corresponding source property in your view model:
private PackageModel _selectedPackage;
public PackageModel SelectedPackage
{
get
{
return _selectedPackage;
}
set
{
_selectedPackage = value;
NotifyOfPropertyChange(() => SelectedPackage);
if (_selectedPackage != null)
{
Amount = _selectedPackage.sessionAmount;
//...
}
else
{
Amount = default(int);
}
}
}
Upvotes: 0
Reputation: 440
These is not Calibro, but you said that it is ok.
I remove the property sessionInMins and sessionAmout to make the answer shorter, you can add it in similar way as other property.
First I add INotifyPropertyChanged to Model, I also make the PascalCase for property (upper letter). PascalCase is not needed but it is most common convention.
public class PackageModel : INotifyPropertyChanged
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
if (value != _Id)
{
_Id = value;
NotifyPropertyChanged();
}
}
}
private string _SessionName;
public string SessionName
{
get { return _SessionName; }
set
{
if (value != _SessionName)
{
_SessionName = value;
NotifyPropertyChanged();
}
}
}
private bool _IsActive;
public bool IsActive
{
get { return _IsActive; }
set
{
if (value != _IsActive)
{
_IsActive = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I Modify VM only to have List of Packages and SelectedPackage, I remove List from name Packages and make it plural addins's. Also make some defualt constructor to fill developer List. As you also suggest, the additional property is not needed right now, just use SelectedPackage later on.
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Packages = new List<PackageModel>()
{
new PackageModel()
{
Id = 1, SessionName="Session1", IsActive = true
},
new PackageModel()
{
Id = 2, SessionName="Session2", IsActive = false
}
};
}
private PackageModel _SelectedPackage;
public PackageModel SelectedPackage
{
get { return _SelectedPackage; }
set
{
if (value != _SelectedPackage)
{
_SelectedPackage = value;
NotifyPropertyChanged();
}
}
}
private List<PackageModel> _Packages;
public List<PackageModel> Packages
{
get { return _Packages; }
set
{
if (value != _Packages)
{
_Packages = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And also add the View. Do manual bindings. When you change the item in the grid the textbox and checkbox will update automatic, also if you change values in textbox or checkbox it will update the DataGrid as well.
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Width="210" Margin="5 5" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="0" Text="{Binding SelectedPackage.SessionName, Mode=TwoWay}"/>
<StackPanel Grid.Row="1">
<CheckBox Margin="5 5" Content="Is Active?" IsChecked="{Binding SelectedPackage.IsActive, Mode=TwoWay}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding Packages,Mode=TwoWay,NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged }"
SelectedItem="{Binding SelectedPackage, Mode=TwoWay}"
Grid.Row="2"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserSortColumns="False"
CanUserReorderColumns="False"
CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Id}" Visibility="Hidden" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding SessionName}" Width="120" IsReadOnly="True"/>
<DataGridCheckBoxColumn IsReadOnly="True" Header="Is Active?" Binding="{Binding IsActive}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Let me know if solution works for you!
Upvotes: 2