0

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

Piero Alberto
  • 3,823
  • 6
  • 56
  • 108

2 Answers2

0

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; }
stijn
  • 34,664
  • 13
  • 111
  • 163
  • Your code seems smart, but it gives me 2 probem, with the two rows for the MainViewModel() method. for otherModel.propertyCHanged it says that "otherModel" doesn't contain any definition of properychanged. – Piero Alberto Sep 22 '14 at 07:19
  • and for SaveCommand = new DelegateCommand(SaveData, saveButtonEnabled), it says that the type of parameters aren't correct. any ideas? – Piero Alberto Sep 22 '14 at 07:20
  • the viewmodel you want to observe must implement INotifyPropertyChanged just like your main view model. I thought that was the case since you are binding xaml to it.. For the command: I just copy-pasted the `DelegateCommand(this.SaveData, this.CanSave);` code from your question so I don't know what is wrong with it – stijn Sep 22 '14 at 07:30
  • Ok for my secondo comment, my error while I was reading your answer. For the other... the other viewModel is a usercontrol, so it already implement "UserControl". is this a problem? is there another way to do it? – Piero Alberto Sep 22 '14 at 07:35
  • I've changed the otherviewmodel and is like the mainviewmodel. implementing the right class. But now, the same row as before, says "An object reference is required for the nonstatic field, method, or property 'member'".. hints for me? – Piero Alberto Sep 22 '14 at 08:00
  • ucImpiantiViewModel uci = new ucImpiantiViewModel(); – Piero Alberto Sep 22 '14 at 08:05
  • uci.PropertyChanged += (s, e) => EnableSaveIfCertainPropertiesChange(e); – Piero Alberto Sep 22 '14 at 08:05
  • now the code compiles and runs, but it still doesn't work... why? – Piero Alberto Sep 22 '14 at 08:06
  • sorry for the comments, but.. I'm studying your code... the IDTTS, REMOTO,... are used in the xaml, from the datacontext.. the viewmodel, how can see them? – Piero Alberto Sep 22 '14 at 08:17
0

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.

Natxo
  • 2,917
  • 1
  • 24
  • 36
  • just now I'm trying to use the event "CollectionChanged" of my observable collection where I update the data. But it gives still some problems.. anyway, in your opinio, can this be a good way to solve my problem? – Piero Alberto Sep 22 '14 at 08:28
  • If you are sure that changes in that collection are indicative of new 'Saveable' data, why not! What i was strongly suggesting is not to use changes in controls themselves to assume changes in your data. – Natxo Sep 22 '14 at 08:52
  • yes, I'm sure but.... it doesn't work!!! the line code ListaImpianti.CollectionChanged += (s, e) => EnableSave();... is it right? what do you suggest? – Piero Alberto Sep 22 '14 at 08:56
  • Looks like it should work... `EnableSave()` is never called? Subscription is properly made? Step by step debug and be sure of this. – Natxo Sep 22 '14 at 09:27
  • I cannot figure it from what you posted, i need too see where `ListaImpianti` is used in your xaml and where is the subscription to `CollectionChanged` made – Natxo Sep 22 '14 at 10:31
  • Where is the collection modified? You know, `CollectionChanged` is only fired when performing `Add` or `Remove` operations on the `ObservableCollection`. – Natxo Sep 22 '14 at 13:28
  • sorry, but... the msdn says "Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.".. and I'm editing the data from the layoutcontrol where the single records are detailed. I'm writing editing the fields.... so, I'm changing the collection, or not? if not, what can I do to trigger on this type of changes? – Piero Alberto Sep 22 '14 at 13:38
  • Changing a property in an item of the collection doesnt fire the event. You can find some implementations out there: http://www.codeproject.com/Tips/694370/How-to-Listen-to-Property-Chang But i still recommend you to call `EnableSave` in the setter of those properties in the ViewModel. – Natxo Sep 22 '14 at 14:36
  • but those properties aren't in the viewmodel.. they are in my model, I used them to populate the list with data from db.. anyway... do you have an example of your idea for solution? – Piero Alberto Sep 22 '14 at 14:38
  • My solution cannot be implemented in present conditions, as the viewmodel has no way to know where changes to data have been made because your model doesnt notify them. At this point you have two options: go the "dirty and fast" way and use view changes to enable save, or modelate your viewmodel to fit your actual model. – Natxo Sep 22 '14 at 15:34
  • I'm a newbie.... how can I do the "dirty and fast"? i'm working in the mvvm pattern – Piero Alberto Sep 22 '14 at 15:36
  • Actually if you want to work with MVVM, the second option is the good one. Anyway in this case the "dirty and fast" is not too bad. Use some "CellValueChanged" event in the grid to enable the save button. – Natxo Sep 22 '14 at 15:43