I'm new to MVVM and I can't seem to find the answer to my problem..
I have 2 Views (Caixa & CaixaDetalhes) where in the Caixa view I have a datagrid displaying all Caixas and in the second view it shows the details and user can "CRUD" but I don't want to close the view when updating or adding with the dialogresult.
My Views:
AssistenciasCaixas:
<DataGrid x:Name="dgCaixas" Grid.Row="1" Style="{StaticResource DataGridResultados}" ItemsSource="{Binding Caixas}" SelectedItem="{Binding CaixaSelecionado}" RowHeight="25" Background="White" >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Visibility="Hidden" Binding="{Binding Id}"/>
<DataGridTextColumn Header="PostoID" Visibility="Hidden" Binding="{Binding Posto.ID}"/>
<DataGridTextColumn Header="IdUtilizador" Visibility="Hidden" Binding="{Binding IdUtilizador}"/>
<DataGridTextColumn Header="Posto" Width="200" Binding="{Binding Posto.Descricao}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Data Caixa" Width="90" Binding="{Binding DataCaixa,StringFormat={}\{0:dd/MM/yyyy\}}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Data Eliminado" Width="100" Binding="{Binding DataEliminado, StringFormat={}\{0:dd/MM/yyyy\}}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Motivo" Width="*" Binding="{Binding Motivo}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Nº Caixa" Width="70" Binding="{Binding NumCaixa}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Exportado" Width="100" Binding="{Binding Exportado}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Observações" Width="200" Binding="{Binding Obs}" CanUserReorder="False" CanUserResize="False" CanUserSort="False" FontSize="15" IsReadOnly="True"/>
<DataGridTextColumn Header="Utilizador" Visibility="Hidden" Binding="{Binding IdUtilizador}"/>
</DataGrid.Columns>
</DataGrid>
CaixaDetalhes
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
<RowDefinition Height="150"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Posto:" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" Margin="0,-5,0,0" />
<ComboBox Grid.Column="0" x:Name="comboBoxPostos" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="300" Margin="0,0,0,5" Style="{DynamicResource ComboBoxProblemas}" ItemsSource="{Binding Postos}" SelectedItem="{Binding Detalhes.Posto}" SelectedValue="{Binding Detalhes.Posto.ID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" DisplayMemberPath="Descricao"/>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Right" Width="1" Stroke="#FF00256D" />
<Label Grid.Column="1" Content="Exportado para o Olisoft?" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="35,0,0,0" Style="{StaticResource lblEditarForms}" FontSize="20"/>
<CheckBox x:Name="cbExportado" Grid.Column="1" IsChecked="{Binding Detalhes.Exportado, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0">
<CheckBox.LayoutTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.LayoutTransform>
</CheckBox>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Stroke="#FF00256D" Grid.ColumnSpan="2" />
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Data do Caixa:" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" FontSize="20"/>
<DatePicker x:Name="dtPickerDataCaixa" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,15" FirstDayOfWeek="Monday" DisplayDateStart="2015-01-01" CalendarStyle="{StaticResource resizedCalendarItem}" SelectedDate="{Binding Detalhes.DataCaixa, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Right" Width="1" Stroke="#FF00256D" />
<Label Grid.Column="1" Content="Data Eliminado:" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" FontSize="20"/>
<DatePicker x:Name="dtPickerDataEliminado" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,15" FirstDayOfWeek="Monday" DisplayDateStart="2015-01-01" CalendarStyle="{StaticResource resizedCalendarItem}" SelectedDate="{Binding Detalhes.DataEliminado, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Right" Width="1" Stroke="#FF00256D" />
<Label Grid.Column="2" Content="Número do Caixa:" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" FontSize="20"/>
<TextBox x:Name="txtNumCaixa" Grid.Column="2" Height="28" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,15" Width="200" MaxLines="1" FontSize="15" Text="{Binding Detalhes.NumCaixa, UpdateSourceTrigger=PropertyChanged}"/>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Stroke="#FF00256D" Grid.ColumnSpan="3" />
</Grid>
<Label Grid.Row="2" Content="Motivo" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" FontSize="20" Margin="0,-6,0,0"/>
<TextBox Grid.Row="2" x:Name="richTextBoxMotivo" Height="100" Width="655" FontFamily="Calibri" FontSize="18" VerticalAlignment="Bottom" BorderBrush="#AAAAAA" BorderThickness="1.5" Margin="0,0,0,8" Text="{Binding Detalhes.Motivo, UpdateSourceTrigger=PropertyChanged}"/>
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Stroke="#FF00256D" Grid.Row="2" />
<Label Grid.Row="3" Content="Observações" VerticalAlignment="Top" HorizontalAlignment="Center" Style="{StaticResource lblEditarForms}" FontSize="20" Grid.RowSpan="2"/>
<TextBox Grid.Row="3" x:Name="richTextBoxObservacoes" Height="100" Width="655" FontFamily="Calibri" FontSize="18" VerticalAlignment="Bottom" BorderBrush="#AAAAAA" BorderThickness="1.5" Margin="0,0,0,10" Text="{Binding Detalhes.Obs, UpdateSourceTrigger=PropertyChanged}" />
<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="1" Stroke="#FF00256D" Grid.Row="3" />
<UniformGrid Grid.Row="4" Columns="4" Height="70" Background="White" >
<Button x:Name="btnGravar" Style="{StaticResource BotaoGridOperacoes}" CommandParameter="{Binding}" Command="{Binding Gravar}">
<DockPanel LastChildFill="True" Width="115">
<Image Source="../../Imagens/save32black.png" Stretch="Fill" Width="32" Height="32" />
<TextBlock x:Name="txtBtnGravar" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF00256D" Text="{Binding Path=AddSave, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"></TextBlock>
</DockPanel>
</Button>
<Button Style="{StaticResource BotaoGridOperacoes}">
<DockPanel LastChildFill="True" Width="115">
<Image Source="../../Imagens/new32Black.png" Stretch="Fill" Width="32" Height="32" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF00256D">Novo</TextBlock>
</DockPanel>
</Button>
<Button Style="{StaticResource BotaoGridOperacoes}" >
<DockPanel VerticalAlignment="Stretch" Width="125">
<Image Source="../../Imagens/bin2.png" Stretch="Fill" Width="32" Height="32"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF00256D">Eliminar</TextBlock>
</DockPanel>
</Button>
<Button x:Name="btnSair" Style="{StaticResource BotaoGridOperacoes}" CommandParameter="{Binding}" Command="{Binding Sair}">
<DockPanel VerticalAlignment="Stretch" Width="100">
<Image Source="/HelpDesk Assist;component/Avancado/Imagens/exit32.png" Stretch="Fill" Width="32" Height="32" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FF00256D">Sair</TextBlock>
</DockPanel>
</Button>
</UniformGrid>
</Grid>
My Models:
Caixa:
public class Caixa : BaseNotifyPropertyChanged
{
private int _id;
private Posto _posto;
private DateTime _dataCaixa;
private DateTime _dataEliminado;
private string _motivo;
private int _numCaixa;
private bool _exportado;
private string _obs;
private int _idUtilizador;
public int Id
{
get => _id;
set => SetField(ref _id, value);
}
public Posto Posto
{
get => _posto;
set => SetField(ref _posto, value);
}
public DateTime DataCaixa
{
get => _dataCaixa;
set => SetField(ref _dataCaixa, value);
}
public DateTime DataEliminado
{
get => _dataEliminado;
set => SetField(ref _dataEliminado, value);
}
public string Motivo
{
get => _motivo;
set => SetField(ref _motivo, value);
}
public int NumCaixa
{
get => _numCaixa;
set => SetField(ref _numCaixa, value);
}
public bool Exportado
{
get => _exportado;
set => SetField(ref _exportado, value);
}
public string Obs
{
get => _obs;
set => SetField(ref _obs, value);
}
public int IdUtilizador
{
get => _idUtilizador;
set => SetField(ref _idUtilizador, value);
}
}
My ViewModels:
CaixasViewModel
public class CaixasViewModel : BaseNotifyPropertyChanged
{
public ObservableCollection<Caixa> Caixas { get; private set; }
public CaixasViewModel()
{
int utilizadorAtual = Properties.Settings.Default.userID;
Caixas = new ObservableCollection<Caixa>();
Caixas = CaixaData.GetCaixasUtilizador(utilizadorAtual);
}
private Caixa _caixaSelecionado;
public Caixa CaixaSelecionado
{
get { return _caixaSelecionado; }
set
{
SetField(ref _caixaSelecionado, value);
Delete.RaiseCanExecuteChanged();
Edita.RaiseCanExecuteChanged();
}
}
public DeleteCaixa Delete { get; private set; } = new DeleteCaixa();
public EditaCaixa Edita { get; private set; } = new EditaCaixa();
public NovoCaixa Novo { get; private set; } = new NovoCaixa();
public class DeleteCaixa : BaseCommand
{
public override bool CanExecute(object parameter)
{
return parameter is CaixasViewModel viewModel && viewModel.CaixaSelecionado != null;
}
public override void Execute(object parameter)
{
CaixasViewModel viewModel = (CaixasViewModel)parameter;
CaixaData.DeleteCaixa(viewModel.CaixaSelecionado.Id);
_ = viewModel.Caixas.Remove(viewModel.CaixaSelecionado);
}
}
public class NovoCaixa : BaseCommand
{
public override bool CanExecute(object parameter)
{
return parameter is CaixasViewModel;
}
public override void Execute(object parameter)
{
CaixasViewModel viewModel = (CaixasViewModel)parameter;
Caixa caixa = new Caixa
{
DataCaixa = DateTime.Now,
DataEliminado = DateTime.Now
};
CaixasDetalhesViewModel caixaDetalhes = new CaixasDetalhesViewModel(caixa);
DetalhesCaixa dc = new DetalhesCaixa(caixaDetalhes)
{
Owner = Application.Current.MainWindow
};
_ = dc.ShowDialog();
if (dc.DialogResult.HasValue && dc.DialogResult.Value)
{
viewModel.Caixas.Add(caixa);
viewModel.CaixaSelecionado = caixa;
}
}
}
public class EditaCaixa : BaseCommand
{
public override bool CanExecute(object parameter)
{
return parameter is CaixasViewModel viewModel && viewModel.CaixaSelecionado != null;
}
public override void Execute(object parameter)
{
CaixasViewModel viewModel = (CaixasViewModel)parameter;
CaixasDetalhesViewModel caixaDetalhes = new CaixasDetalhesViewModel(viewModel.CaixaSelecionado);
DetalhesCaixa detCaixa = new DetalhesCaixa(caixaDetalhes)
{
Owner = Application.Current.MainWindow
};
_ = detCaixa.ShowDialog();
}
}
}
CaixasDetalhesViewModel:
public class CaixasDetalhesViewModel : BaseNotifyPropertyChanged
{
public ObservableCollection<Posto> Postos { get; set; }
public Caixa Detalhes { get; set; }
public CaixasDetalhesViewModel(Caixa detalhes)
{
Postos = new ObservableCollection<Posto>();
Postos = PostoData.GetPostosSimples();
Detalhes = detalhes;
EscolheFinal();
}
private string _addSave = "Inserir";
public string AddSave
{
get => _addSave;
set
{
_addSave = value;
RaisePropertyChanged("AddSave");
}
}
public void EscolheFinal()
{
AddSave = Detalhes.Id == 0 ? "Inserir" : "Gravar";
}
public Action DialogResult { get; set; }
public Action CloseWindow { get; set; }
public GravarCaixa Gravar { get; private set; } = new GravarCaixa();
public SairCaixa Sair { get; private set; } = new SairCaixa();
public class GravarCaixa : BaseCommand
{
public override bool CanExecute(object parameter)
{
return parameter is CaixasDetalhesViewModel viewModel;
}
public override void Execute(object parameter)
{
CaixasDetalhesViewModel viewModel = (CaixasDetalhesViewModel)parameter;
viewModel.Detalhes.IdUtilizador = Properties.Settings.Default.userID;
CaixaData.AdicionaCaixa(viewModel.Detalhes);
viewModel.EscolheFinal();
viewModel.DialogResult();
}
}
public class SairCaixa : BaseCommand
{
public override bool CanExecute(object parameter)
{
return parameter is CaixasDetalhesViewModel viewModel;
}
public override void Execute(object parameter)
{
CaixasDetalhesViewModel viewModel = (CaixasDetalhesViewModel)parameter;
viewModel.CloseWindow();
}
}
}
Overall:
I can CRUD with DialogResult but I need to be able to insert/update/delete "Caixa" in the CaixaDetalhes View without closing the view
EDIT: How can I Update the Caixa Model just when clicking the save button? I've try using "UpdateSourceTrigger=Explicit" but when trying to change Caixa nothing happens..
I'm not using any framework and I'd like to keep it that way if possible
CodePudding user response:
I recommend to pass a reference of the save command and the current modified item from the CaixasViewModel to the CaixasDetalhesViewModel. Then let the CaixasViewModel modify its Caixas collection, as it is the owner.
You should not show dialogs from the view model. Show it from code-behind (see example below).
The following example should give you a raw idea about the pattern: let the main view model create and initialize the dialog view model with a corresponding save command and the item to modify (add and edit). The dialog view model only exposes the item for modification to the user and executes the save command that it received from the main view model. The main view model knows how to handle the target item e.g., create new instance (create new dialog) or copy instance (edit dialog).
The main idea is to create a copy of the original item model that you want to edit. When the save button is clicked, the changes are committed by copying the new data back to the original model (edit a copy).
The example also makes use of the reusable RelayCommand, to help to improve readability by reducing code complexity. You can find the original implementation at Microsoft Docs: Relaying Command Logic. Simply copy the short code to a class named RelayCommand in your project and use it instead of implementing new command classes for each command.
CaixasViewModel.cs
class CaixasViewModel
{
public ObservableCollection<Caixa> Caixas { get; private set; }
// The "Save" command of the create dialog.
public ICommand AddCaixaCommand => new RelayCommand(ExecuteAddCaixa);
// The "Save" command of the edit dialog
public ICommand UpdateCaixaCommand
=> new RelayCommand(ExecuteUpdateCaixa, commandParameter => this.CaixaSelecionado != null);
public ICommand RemoveCaixaCommand => new RelayCommand(ExecuteRemoveCaixa);
// Clears the current EditSource by creating a new empty Caixa item
public ICommand NewCaixaCommand => new RelayCommand(ExecuteNewCaixa);
private CaixasDetalhesViewModel CurrentCaixasDetalhesViewModel { get; set; }
public CaixasDetalhesViewModel GetEditCaixaViewModel
{
var editSource = this.CaixaSelecionado.Clone();
this. CurrentCaixasDetalhesViewModel =
new CaixasDetalhesViewModel(this.ReplaceCaixaCommand, editSource);
return this.CurrentCaixasDetalhesViewModel;
}
public CaixasDetalhesViewModel GetCreateCaixaViewModel
{
var editSource = new Caixa();
this.CurrentCaixasDetalhesViewModel =
new CaixasDetalhesViewModel(this.AddCaixaCommand, editSource);
return this.CurrentCaixasDetalhesViewModel;
}
private void ExecuteAddCaixa(object commandParameter)
{
var createdCaixa = (Caixa)commandParameter;
this.Caixas.Add(createdCaixa);
// Create a new temp copy to avoid editing the original instance directly
var editableCreatedCaixa = createdCaixa.Clone();
this.CurrentCaixasDetalhesViewModel.EditSource = editableCreatedCaixa;
}
private void ExecuteNewCaixa(object commandParameter)
{
// Update the dialog with a new empty Caixa item
var emptyCaixa = new Caixa();
this.CurrentCaixasDetalhesViewModel.EditSource = emptyCaixa;
}
private void ExecuteUpdateCaixa(object commandParameter)
{
Caixa editedCaixa = (Caixa)commandParameter;
var originalCaixa = this.Caixas.First(caixa => caixa.Id == editedCaixa.Id);
// Copy data
originalCaixa.Posto = editedCaixa.Posto;
...
}
private void ExecuteRemoveCaixa(object commandParameter)
=> this.Caixas.Remove((Caixa)commandParameter);
}
CaixasDetalhesViewModel.cs
class CaixasDetalhesViewModel : INotifyPropertyChanged
{
public Caixa EditSource { get; set; }
public ICommand SaveCommand { get; }
public CaixasDetalhesViewModel(
ICommand saveCommand,
Caixa editSource)
{
this.CommandProvider = saveCommand;
this.EditSource = editSource;
}
}
AssistenciasCaixas.xaml
<!-- DataContext is the CaixasViewModel -->
<Window>
<StackPanel>
<DataGrid x:Name="dgCaixas" />
<Button Content="Open Edit Dialog" Click="OnEditDialogButtonClicked" />
<Button Content="Open Create New Dialog" Click="OnCreateDialogButtonClicked" />
</StackPanel>
</Window>
AssistenciasCaixas.xaml.cs
partial class AssistenciasCaixas : Window
{
private void OnEditDialogButtonClicked(object sender, EventArgs e)
{
var viewModel = this.DataContex as CaixasViewModel;
CaixasDetalhesViewModel caixaDetalhes = viewModel.GetEditCaixaViewModel();
var dc = new DetalhesCaixa(caixaDetalhes);
_ = dc.ShowDialog(); // Owner is automatically set on ShowDialog()
// The following code is now handled in the CaixasViewModel
//if (dc.DialogResult.HasValue && dc.DialogResult.Value)
//{
// viewModel.Caixas.Add(caixa);
// viewModel.CaixaSelecionado = caixa;
//}
}
private void OnCreateDialogButtonClicked(object sender, EventArgs e)
{
var viewModel = this.DataContex as CaixasViewModel;
CaixasDetalhesViewModel caixaDetalhes = viewModel.GetCreateCaixaViewModel();
var dc = new DetalhesCaixa(caixaDetalhes);
_ = dc.ShowDialog(); // Owner is automatically set on ShowDialog()
// The following code is now handled in the CaixasViewModel
//if (dc.DialogResult.HasValue && dc.DialogResult.Value)
//{
// viewModel.Caixas.Add(caixa);
// viewModel.CaixaSelecionado = caixa;
//}
}
}
Caixa.cs
public class Caixa : ICloneable
{
public int Id
{
get => _id;
set => SetField(ref _id, value);
}
...
// Assumes that the property Posto is not editable via dialog,
// otherwise clone Posto too (deep clone).
object ICloneable.Clone() => MemberwiseClone();
public Caixa Clone() => ((ICloneable)this).Clone() as Caixa;
}
CaixaDetalhes.xaml
<!-- DataContext is the CaixasDetalhesViewModel -->
<Window>
<StackPanel>
<TextBox Text="{Binding EditSource.Motivo}" />
<Button Content="Save"
Command="{Binding SaveCommand}"
CommandParameter="{Binding EditSource}" />
</StackPanel>
</Window>

