Search code examples
wpfxamlmvvmcommandcanexecute

MVVM: command and canExecute flag


I'm working with my first command with a dynamic flag canExecute.

I have my save command, that it must enabled only when user makes some data changes.

I was thinking about binding an action when the mods are made, but I get errors, maybe this isn't the right way.

This is my xaml (as you can see, all my fields are in a layout control):

            <dxlc:LayoutGroup Header="Configurazione tecnica" View="GroupBox" Orientation="Vertical">
                <dxlc:LayoutItem Label="Tipo sistema">
                    <dxe:ComboBoxEdit IsTextEditable="False" EditValue="{Binding IDTTS}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.tts}"  />
                </dxlc:LayoutItem>
                <dxlc:LayoutItem Label="Locazione">
                    <!--<dxe:ComboBoxEdit  EditValue="{Binding REMOTO}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},Path=DataContext.locations}"  />-->
                    <StackPanel Margin="0" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Left">
                        <RadioButton x:Name="rd_LOCALE" Content="{DynamicResource Locale}" Margin="10,0,0,0" VerticalAlignment="Center" GroupName="Location" IsChecked="True" Panel.ZIndex="9" TabIndex="10" />
                        <RadioButton Content="{DynamicResource Remoto}" Margin="10,0,6,0" x:Name="rd_REMOTO" Tag="PRISMA" VerticalAlignment="Center" IsChecked="{Binding REMOTO}" GroupName="Location" Panel.ZIndex="10" TabIndex="11" />
                    </StackPanel>
                </dxlc:LayoutItem>
                <dxlc:LayoutItem Label="Tipo di connessione">
                    <!--<dxe:ComboBoxEdit  EditValue="{Binding TIPOCONN}" />-->
                    <StackPanel Margin="0" Orientation="Horizontal" VerticalAlignment="Center">
                        <RadioButton Content="{DynamicResource Terminale}" Margin="10,0,0,0" x:Name="rd_TIPOCONN" Tag="PRISMA" VerticalAlignment="Center" GroupName="TipoConn" IsChecked="True" Panel.ZIndex="11" TabIndex="12" />
                        <RadioButton x:Name="rd_SLAVE" Content="Slave" Margin="10,0,6,0" Tag="PRISMA" VerticalAlignment="Center" IsChecked="{Binding TIPOCONN}" GroupName="TipoConn" Panel.ZIndex="12" TabIndex="13" />
                    </StackPanel>
                </dxlc:LayoutItem>
            </dxlc:LayoutGroup>

            <dxlc:LayoutGroup Header="Centralina STK" View="GroupBox" Orientation="Vertical">
                <dxlc:LayoutItem >
                    <!--<dxe:ComboBoxEdit  EditValue="{Binding SERMATIC}" />-->
                    <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,6">
                        <RadioButton x:Name="rd_sermatic" Content="{DynamicResource SI}" Margin="10,0,0,0"  Tag="PRISMA" VerticalAlignment="Center" Width="100" HorizontalAlignment="Left" IsChecked="{Binding SERMATIC}" GroupName="stk" Panel.ZIndex="13" TabIndex="14" />
                        <RadioButton x:Name="rd_sermaticNO" Content="{DynamicResource NO}" Margin="10,0,0,0" Tag="PRISMA" VerticalAlignment="Center" Width="100" HorizontalAlignment="Left" GroupName="stk" IsChecked="True" Panel.ZIndex="14" TabIndex="15" />
                    </StackPanel>
                </dxlc:LayoutItem>
                <dxlc:LayoutItem >
                    <!--<dxe:ComboBoxEdit  EditValue="{Binding SERMATICCOM}"/>-->
                    <UniformGrid Rows="1" Columns="2" DockPanel.Dock="Top" Margin="4,0,4,4" IsEnabled="{Binding IsChecked, ElementName=rd_sermatic}">
                        <TextBlock Margin="0" TextWrapping="Wrap" Text="{DynamicResource PortaCOM}" TextAlignment="Right" VerticalAlignment="Center" HorizontalAlignment="Right"/>
                        <ComboBox x:Name="cmb_SERMATICCOM" Height="23" Margin="10,2,0,0" Panel.ZIndex="15" TabIndex="16">
                            <ComboBoxItem Content="----" />
                            <ComboBoxItem Content="COM1" />
                            <ComboBoxItem Content="COM2" />
                            <ComboBoxItem Content="COM3" />
                            <ComboBoxItem Content="COM4" />
                            <ComboBoxItem Content="COM5" />
                            <ComboBoxItem Content="COM6" />
                            <ComboBoxItem Content="COM7" />
                            <ComboBoxItem Content="COM8" />
                        </ComboBox>
                    </UniformGrid>
                </dxlc:LayoutItem>
            </dxlc:LayoutGroup>
        </dxlc:LayoutControl>

And this is my MainWindowVIewModel, where I define the command and the canExceute:

private bool CanSave()
{
    return SaveButtonEnabled;
}

public ICommand SaveCommand { get; private set; }

void EnableSave(NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Replace)
        SaveButtonEnabled = true;
}

private bool p_saveButtonEnabled=false;
public bool SaveButtonEnabled
{
    get{ return p_saveButtonEnabled; }
    set
        {
        p_saveButtonEnabled = value;
        base.RaisePropertyChangedEvent("SaveButtonEnabled");
    }
}

private void SaveData()
{
    MainWindow.dbContext.SaveChanges();
    SaveButtonEnabled = false;
    //base.RaisePropertyChangedEvent("SaveButtonEnabled");
}

And when I populated my observableColletion, where I change data in the binded user control, I have:

ListaImpianti.CollectionChanged += (s, e) => EnableSave(e);

ListaImpianti is binded to the xaml in this way:

<DockPanel Grid.Row="1" Margin="0,60,0,0">
    <dxg:GridControl x:Name="lst1" ItemsSource="{Binding ListaImpianti}"  EnableSmartColumnsGeneration="True" FilterCriteria="{Binding FilterCriteria, ElementName=searchControl}"  MaxHeight="500" Height="266" VerticalAlignment="Top" Margin="0,-27,0,0" Width="332" ShowBorder="False">
        <dxg:GridControl.Columns>
            <dxg:GridColumn x:Name="CODICE" Binding="{Binding CODICE}" FieldName="CODICE"/>
            <dxg:GridColumn x:Name="NOME" Binding="{Binding NOME}" FieldName="NOME"/>
        </dxg:GridControl.Columns>
        <dxg:GridControl.View>
            <dxg:TableView AllowPerPixelScrolling="True" AllowEditing="False" ShowGroupPanel="False" ShowFilterPanelMode="Never"  />
        </dxg:GridControl.View>
     </dxg:GridControl>
</DockPanel>

ListaImpianti is defined as:

 public ObservableCollection<TabImpianti> ListaImpianti
 {
    get { return p_ListaImpianti; }
    set
    {
        p_ListaImpianti = value;
        base.RaisePropertyChangedEvent("ListaImpianti");
    }
}
[...]
p_ListaImpianti = new ObservableCollection<TabImpianti>();
var query2 = (from r in MainWindow.dbContext.TabImpianti select r);
foreach (TabImpianti ti in query2) { p_ListaImpianti.Add(ti); }

But enable save is never called.. why?

Piero


Solution

  • If the user changes something, the viewmodel will know it, right? To me is clear that the flag SaveButtonEnabled should be changed by the viewmodel itself, not by any command bound to the view.

    For example, if user changes SERMATIC property, the setter of that property is where you have to change the flag if necesary.

    Side note: move those base.RaisePropertyChangedEvent("SaveButtonEnabled") snippets to the setter of the SaveButtonEnabled property.