tags:

views:

30

answers:

3

I'm trying to plan an mvvm based infrastructure, i decided that the view will be bounded directly to the entities through the vm. (the vm will hold the entity reference, and binding will be o entity.propertyName...)

now i got a problem, when user start editing fields in one view, i wanna lock(make readonly) all other view that bound to the entity being under edit.

so my question is: what changes do i need to do in my design for having the ability to know who(what view) started editing first, and when he finished.. and how to know about changed that came not from the ui(for not locking anything)

A: 

It would really depend on your program structure. I have some similar requirements and I did this in my primaryshell view...

<Grid wpfi:VisualStateAssistant.CurrentVisualState="{Binding Path=CurrentVisualState}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="640" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="480" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="ActiveFormStateGroup">
                <VisualStateGroup.Transitions>

                </VisualStateGroup.Transitions>
                <VisualState x:Name="Searching">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchHeaderView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchNavigationView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchResultsView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsModuleShell">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsModuleNavigationView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>

                </VisualState>
                <VisualState x:Name="DoctorEdit">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchHeaderView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchNavigationView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="SearchResultsView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsModuleShell">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsModuleNavigationView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>

                </VisualState>

            </VisualStateGroup>
            <VisualStateGroup x:Name="VisualAlertsStateGroup">
                <VisualStateGroup.Transitions>

                </VisualStateGroup.Transitions>
                <VisualState x:Name="DialogShowing">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="DialogNotShowing">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="IsWaiting">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="WaitControl">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="IsNotWaiting">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="WaitControl">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

            </VisualStateGroup>
            <VisualStateGroup x:Name="DialogFormsStateGroup">
                <VisualStateGroup.Transitions>

                </VisualStateGroup.Transitions>
                <VisualState x:Name="ShowContactInfoEdit">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="ContactInfoDatagridView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>

                </VisualState>
                <VisualState x:Name="HideContactInfoEdit">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="ContactInfoDatagridView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />

                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>

                </VisualState>
                <VisualState x:Name="ShowDoctorTaxonomyEdit">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsTaxonomyEditView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Visible}" />

                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="HideDoctorTaxonomyEdit">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                                       Storyboard.TargetName="DoctorsTaxonomyEditView">
                            <DiscreteObjectKeyFrame KeyTime="0"
                                                    Value="{x:Static Visibility.Collapsed}" />

                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <DockPanel Grid.Column="0"
                   Grid.Row="0"
                   Grid.ColumnSpan="3"
                   Grid.RowSpan="3">

            <!--TOP-->
            <Grid ShowGridLines="False"
                  DockPanel.Dock="Top">

                <views:SearchHeaderView x:Name="SearchHeaderView" />

            </Grid>
            <!--BOTTOM-->
            <Grid ShowGridLines="false"
                  DockPanel.Dock="Bottom">

                <views:SearchNavigationView x:Name="SearchNavigationView" />
                <views:DoctorsModuleNavigationView  x:Name="DoctorsModuleNavigationView"
                                                    Visibility="Collapsed" />

            </Grid>

            <!--FILL-->
            <Grid ShowGridLines="False">

                <views:SearchResultsView x:Name="SearchResultsView" />
                <views:DoctorsModuleShell x:Name="DoctorsModuleShell"
                                          Visibility="Collapsed" />
            </Grid>

        </DockPanel>
        <Rectangle x:Name="rectangle"
                   RadiusX="2"
                   RadiusY="2"
                   Grid.Column="0"
                   Grid.Row="0"
                   Grid.ColumnSpan="3"
                   Grid.RowSpan="3"
                   Fill="{StaticResource modalFormHitTestRectangleBrush}"
                   IsHitTestVisible="True"
                   Visibility="Collapsed" />

        <controls:WaitingControl x:Name="WaitControl"
                                 Width="100"
                                 Height="100"
                                 Visibility="Collapsed"
                                 Grid.Column="1"
                                 Grid.Row="1" />

        <views:ContactInfoDatagridView x:Name="ContactInfoDatagridView"
                                       Grid.Column="1"
                                       Grid.Row="1"
                                       Visibility="Collapsed" />
        <views:DoctorsTaxonomyEditView x:Name="DoctorsTaxonomyEditView"
                                       Grid.Column="1"
                                       Grid.Row="1"
                                       Visibility="Collapsed" />

    </Grid>

Basically I databound my visualstatemanager to a property on my primary viewmodel that would trigger various visual states depending on an enum I created. I got the best help for doing that from Karl Shifflets Stuff Application.

ecathell
A: 

An option would be to use an EventAggegator or use the Mediator pattern to alert other view models that a particular entity instance is under edit. Those viewmodels can set a property IsReadonly = true which can be bound in xaml to make the controls readonly.

Just rasie an out of edit event to tell those viewmodels that it is no longer in reaonly mode.

If you are binding directly to the entity it should be implementing INotifyPropertyChanged which your view model can listen to in order to detect the first edit taking place.

HTH

aqwert
1. what if an entity changed not from any view?2. how will i know when edit ends and i can release lock?3. how will i know(or another programmer who is using my infrastructure) to register the view model on the property changed event for each entity he is holding in the view model?
Chen Kinnrot
1. If it implements INotifyPropertyChanged the eventing mechanism should still work as I described.2. I would imagine there would be some action the user takes that defines this such as a Save, Apply or Cancel button. Just code up the handler to raise the event3. This is defined as the behaviour of the view and thus coded accordingly in the view model. You can create a base class that all your view models inherit from that attached, raise and listen for these changes so all you have to say is to inherit from that class
aqwert
+1  A: 

First you'll have to define exactly what it means for user to be "editing" an entity.

  1. What action on the user's part signals they are beginning to edit an entity? Clearly when someone types something in a TextBox bound to a model field they are editing the entity. But what if someone clicks a button in the view that clears a property value in the model or sets it to a predefined value. Is that considered "editing" the entity? What if they change the settings of the view so that it shows fewer options and a ComboBox switches the value in the model to a new value because the old value is no longer listed?

  2. What action on the user's part signals they are done editing an entity? Are they done when the focus leaves the control they are on? When the focus leaves the view? When they click "Ok" or "Save"?

Depending on the answers to these questions, several simple solutions present themselves. I'll explain one simple answer. If it doesn't apply please clarify the answers to the above questions.

Supposing:

  1. Any change made to a model object except a data refresh from the database is considered editing, whether it be done with a button, checkbox, etc
  2. The view that contains the focus is considered to be the one who is making the change
  3. "Editing" ends when the view loses focus or the user saves the data.

Then the following will work:

  1. Add a "Reloading" property either as a static property or somewhere within your model
  2. In your data reloading code, set this property "true" and in a finally block set it "false" again
  3. In your model object (or in a data structure shared between your view models and mapped from the model using a weak dictionary), add a property that contains the view model of the view that is currently editing the entity
  4. In your view, add a PropertyChanged handler to your model object when the view model is attached
  5. In the handler, if: a. The Reloading flag is false, b. No view is currently editing the entity, and c. This view's IsKeyboardFocusWithin is true, then record the fact that this view is currently editing the entity
  6. In every view model, attach a handler to the "CurrentlyEditorView" property mentioned in step 3 (either directly or through the separate data structure). Whenever this propety changed, if it is non-null and not the current view model, set an inherited "Locked" property causing the view to lock.
  7. In the view, add a LostFocus event handler that checks if the model's current view is this one, and if so clears it.
  8. Also add a handler for the Save command that does the same thing.
Ray Burns
is this your best practice suggestion?
Chen Kinnrot
what if the entity is being changed from code with no user editing nothing?
Chen Kinnrot
your first 2 supposes are ok the 3 i'm not sure...
Chen Kinnrot
i wanna allow lost focus without ending the edit cause maybe i got validation errors..
Chen Kinnrot
In answer to your question "Is this your best practice suggestion?": No, it is not! You asked for a way to make a UI prevent editing in one view whenever editing is started in another view. This is not best practice because it is confusing to the user. Best practice is to give your entire application the same view of the data. This means you should be able to edit an entity simultaneously in multiple views and have it work correctly. Unless you are using modal dialog boxes with Ok and Cancel buttons, in which case the dialog box gets its own copy of the data.
Ray Burns