MVVM: command and canExecute flag

2019-09-17 03:24发布

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

2条回答
Anthone
2楼-- · 2019-09-17 04:18

Usually if you want to have something done when a property changes you'd add a listener for the PropertyChangedEvent. This doesn't change when there are many properties, here's a possible implementation: register a listener for the PropertyChanged event of the viewModel containing the properties to monitor and check if the name of the changed property matches one of those you want to monitor. If so, enable the save button.

MainWindowViewModel()
{
  otherVewModel.PropertyChanged += ( s, e ) => EnableSaveIfCertainPropertiesChange( e );
}

List<string> propertiesTriggeringEnableSave = new List<string> {
  "IDTTS", "REMOTO", "TIPOCONN" //and so on
};

void EnableSaveIfCertainPropertiesChange( PropertyChangedEventArgs e )
{
  if( propertiesTriggeringEnableSave.Contains( e.PropertyName ) )
    SaveButtonEnabled = true;
}

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

Also some general guidelines: note I changed the SaveButtonEnabled to how it is commonly used in mvvm: this is better then what you are using now because you don't have to repeat the (prone to errors) RaisePropertyChangedEvent line. Also I wouldn't bother with the lazy initialization for commands, the marginal (if any) performance gain yields uglier code. Code would in my opinion be shorter and more readable if your command were initialized in the constructor like

MainViewModel()
{
  SaveCommand = new DelegateCommand( Save, CanSave );
}

public ICommand SaveCommand{ get; private set; }
查看更多
Evening l夕情丶
3楼-- · 2019-09-17 04:27

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.

查看更多
登录 后发表回答