Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

WPF MVVM - View Model Properties called more than once

Îåêßíçóå áðü ôï ìÝëïò StrouMfios. Τελευταία δημοσίευση από το μέλος StrouMfios στις 09-02-2011, 09:52. Υπάρχουν 2 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  07-02-2011, 19:18 63081

    WPF MVVM - View Model Properties called more than once

    Καλησπέρα σε όλους, 

    προσπαθώ να φτιάξω μια μικρή εφαρμογή σε wpf με το Pattern mvvm. 
    Στο data layer για βάση δεδομένων χρησιμοποιώ .sdf file και Entity Framework.

    H εφαρμογή έχει 3 views και ενα ViewModel

    Στο dataContext του mainWindow κάνω bind το ViewModel και τα άλλα δυο userControls παίζουν με το ίδιο ViewModel.
    Σε ένα UserControl έχω ένα property με όνομα isValid το οποίο  το ενημερώνω  απο το mainWindow.xaml.cs.
    Το πρόβλημα είναι οτι ενώ το property ενημερώνετε σωστά, για κάποιο λόγο γίνεται συνέχεια initialize με αποτέλεσμα να φέρνει πάντα την default τιμή (false δηλαδή).
    Αυτό συμβαίνει στο AddItemsView.xaml

    PLEASE!!!!!!!!! HELP!!!!!!
    Ευχαριστώ εκ των προτέρων για την οποιαδήποτε βοήθεια

    Παρακάτω είναι ο κώδικας

    MainWindow.xaml
    <Window x:Class="ToDoListTest.Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:ToDoListTest.Views" 
            xmlns:ViewModels="clr-namespace:ToDoListTest.ViewModels"     
            xmlns:tb="clr-namespace:Hardcodet.Wpf.TaskbarNotification;assembly=Hardcodet.Wpf.TaskbarNotification"
            Title="MainWindow" MinHeight="380"  Width="620" Height="300" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" ResizeMode="CanMinimize">
    
        <Window.DataContext>
            <ViewModels:MainWindowViewModel />
        </Window.DataContext>
    
        <DockPanel>
            <tb:TaskbarIcon
          x:Name="MyNotifyIcon"
          IconSource="/Images/Icons/Error.ico"
          ToolTipText="ToDoList" />
            
            <StackPanel DockPanel.Dock="Top">
                <DockPanel>
                    <local:ViewItemsList DockPanel.Dock="Left"></local:ViewItemsList>
                    <local:AddItemsView DockPanel.Dock="Right" BorderBrush="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                </DockPanel>
            </StackPanel>
            <Grid Background="#FFD8D8D8">
                <local:StatusBar DockPanel.Dock="Bottom"></local:StatusBar>
            </Grid>
        </DockPanel>
    
    </Window>
    mainWIndow.xaml.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Threading;
    using Domain;
    using Samples;
    using ToDoListTest.ViewModels;
    
    namespace ToDoListTest.Views {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window {
    
            private readonly HashSet<ValidationError> errors = new HashSet<ValidationError>();
            private Lazy<MainWindowViewModel> viewModel;
    
            public MainWindow() {
                InitializeComponent();
                InitializeValidaton();
                SetInterval();
            }
    
            void InitializeValidaton() {
                viewModel = new Lazy<MainWindowViewModel>();
                Validation.AddErrorHandler(this, ErrorChangedHandler);
            }
    
            private void ErrorChangedHandler(object sender, ValidationErrorEventArgs e) {
                if (e.Action == ValidationErrorEventAction.Added) {
                    errors.Add(e.Error);
                } else {
                    errors.Remove(e.Error);
                }
    
                viewModel.Value.IsValid = !errors.Any();
            }
    
            void SetInterval() {
                var dispatcherTimer = new DispatcherTimer();
                dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
                dispatcherTimer.Interval = new TimeSpan(0, 1, 0);
                dispatcherTimer.Start();
            }
    
            private void dispatcherTimer_Tick(object sender, EventArgs e) {
                try {
                    var unitOfWork = new EFUnitOfWork();
                    var itemRepository = new ItemRepository(new EFRepository<Item>(), unitOfWork);
                    var item = itemRepository.GetActive();
                    if (item != null) {
                        //Update the status
                        item.Status = true;
                        itemRepository.Save();
                        unitOfWork.Save();
                        var balloon = new FancyBalloon();
                        balloon.BalloonText = item.Name;
                        balloon.BalloonDescription = item.Description;
                        //show balloon and close it after 4 seconds
                        MyNotifyIcon.ShowCustomBalloon(balloon, PopupAnimation.Slide, 4000);
                    }
                } catch (Exception ex) {
                    //swallow
                }
            }
        }
    }
    AddItemsView.xaml
    <UserControl 
                 x:Class="ToDoListTest.Views.AddItemsView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="310" d:DesignWidth="200">
        <Grid Height="312">
            <Grid.RowDefinitions>
                <RowDefinition Height="55" />
                <RowDefinition Height="89" />
                <RowDefinition Height="62" />
                <RowDefinition Height="56" />
                <RowDefinition Height="37*" />
            </Grid.RowDefinitions>
            <Label Content="Name" Height="28" HorizontalAlignment="Left" Margin="12,0,0,0" Name="label1" VerticalAlignment="Top" />
            <Label Content="Description" Height="28" HorizontalAlignment="Left" Margin="12,51,0,0" Name="label2" VerticalAlignment="Top" Grid.RowSpan="2" />
            <TextBox Text="{Binding Path=ActiveItem.Name, Mode=TwoWay,
                UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true,ValidatesOnExceptions=True, NotifyOnValidationError=True}" Height="23" HorizontalAlignment="Left" Margin="12,22,0,0" VerticalAlignment="Top" Width="176" />
            <TextBox Height="56" Text="{Binding Path=ActiveItem.Description, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true}" HorizontalAlignment="Left" Margin="12,22,0,0" VerticalAlignment="Top" Width="176" TextWrapping="Wrap" Grid.Row="1" />
            <Button Content="Save" Command="{Binding Path=SaveItem}" CommandParameter="{Binding}" Height="25" HorizontalAlignment="Left" Margin="113,14,0,0" Name="button1" VerticalAlignment="Top" Width="75" Grid.Row="4" />
            <Label Content="Date to act" Height="28" HorizontalAlignment="Left" Margin="12,0,0,0" Name="label4" VerticalAlignment="Top" Grid.Row="2" />
            <DatePicker SelectedDate="{Binding Path=ActiveItem.DateToAct, Mode=TwoWay}" Height="25" HorizontalAlignment="Left" Margin="12,0,0,5" Name="datePicker1" VerticalAlignment="Bottom" Width="176" Grid.Row="2" />
            <Label Content="Time to act" Height="28" HorizontalAlignment="Left" Margin="14,0,0,0" Name="label3" VerticalAlignment="Top" Grid.Row="3" />
            <ComboBox Grid.Row="3" SelectedItem="{Binding Path=ActiveItem.Hour}" ItemsSource="{Binding Path=Hours}" DisplayMemberPath="" Height="23" HorizontalAlignment="Left" Margin="14,23,0,0" Name="hourDDL" VerticalAlignment="Top" Width="40" />
            <ComboBox Grid.Row="3" SelectedItem="{Binding Path=ActiveItem.Minute}"  ItemsSource="{Binding Path=Minutes}" Height="23" HorizontalAlignment="Right" Margin="0,23,100,0" Name="minuteDDL" VerticalAlignment="Top" Width="40" />
        </Grid>
    </UserControl>
    AddItemsView.xaml.cs
    using System;
    using System.Windows.Controls;
    using ToDoListTest.ViewModels;
    
    namespace ToDoListTest.Views {
        /// <summary>
        /// Interaction logic for AddItemsView.xaml
        /// </summary>
        public partial class AddItemsView : UserControl {
    
            public AddItemsView() {
                InitializeComponent();
            }
        }
    }
    MainWindowViewModel.cs
    Με κόκκινο είναι η μέθοδος που γίνεται συνέχεια initialize
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;
    using System.Windows.Threading;
    using Domain.Infrastructure;
    using Samples;
    using Domain;
    using System.Collections.ObjectModel;
    using ToDoListTest.Services;
    using System.Collections.Generic;
    using System.Waf.Applications;
    
    namespace ToDoListTest.ViewModels {
        public class MainWindowViewModel : ObservableObject {
    
            #region Declarations
            readonly IDialogService _dialog;
            #endregion
    
            #region Properties
             
          ......
    
            private bool _isValid;
            public bool IsValid {
                get { return _isValid; }
                set {
                    _isValid = value;
                    RaisePropertyChanged("IsValid");
                }
            }
          
            
            #endregion
    
            #region Constructors
    
            public MainWindowViewModel() : this(new ModalDialogService()) { }
    
            public MainWindowViewModel(IDialogService dialog) {
                this._dialog = dialog;
                ItemResults = new ObservableCollection<Item>();
                ShowNewItemForm = "Hidden";
                Status = "Ready";
                LoadItemResults();
                PrepareHoursAndMinutes();
            }
            #endregion
    
            #region Commands
            public ICommand AddNewItem {
                get { return new RelayCommand(AddNewItemExecute); }
            }
    
            public ICommand SaveItem {
                get { return new RelayCommand(SaveItemExecute,CanSaveItem); }
            }
    
          .........
    
            #endregion
    
            #region Methods
    
            public void SaveItemExecute() {
                if (ActiveItem == null) {
                    _dialog.ShowMessage("Please select an item from the list to update it, or click the {New} button to create a new item.", "Oops!!", DialogButton.OK, DialogImage.Error);
                    return;
                }
    
                if (ActiveItem.EntityKey != null) {
                    UpdateItem();
                    return;
                }
    
                try {
                    var i = new Item {
                        DateCreated = DateTime.Now,
                        DateToAct = this.ActiveItem.DateToAct,
                        Description = this.ActiveItem.Description,
                        Name = this.ActiveItem.Name,
                        TimeToAct = ActiveItem.TimeToAct,
                        Status = false
                    };
    
                    var unitOfWork = new EFUnitOfWork();
                    var itemRepository = new ItemRepository(new EFRepository<Item>(), unitOfWork);
                    itemRepository.Add(i);
                    unitOfWork.Save();
                    ItemResults.Add(i);
                    ActiveItem = new Item();
                    Status = "Saved!";
                } catch (Exception ex) {
    
                    this._dialog.ShowException(ex.Message);
                }
            }
    
            public bool CanSaveItem() {
                return IsValid;
            }
    
          ..............
            #endregion
        }
    }
    Entity
    using System;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Waf.Foundation;
    
    namespace Domain {
        internal interface IItem {
            [Required(ErrorMessage = "Please enter Title")]
            [StringLength(100, ErrorMessage = "The maximum length is 100")]
            string Name { get; set; }
        }
    
        [MetadataType(typeof(IItem))]
        public partial class Item : IItem, IDataErrorInfo, IFormattable {
            [NonSerialized]
            private readonly DataErrorInfoSupport dataErrorInfoSupport;
    
    
            public Item() {
                ID = Guid.NewGuid();
                dataErrorInfoSupport = new DataErrorInfoSupport(this);
            }   
    
    
            string IDataErrorInfo.this[string memberName] { get { return dataErrorInfoSupport[memberName]; } }
    
            public string Error {
                get {
                    return dataErrorInfoSupport.Error;
                }
            }
        }
    }
    Έχω κάνει και αυτο το post, αλλα είπα να κάνω καινούριο για να είναι πιο ξεκάθαρο το πρόβλημα που αντιμετωπίζω.



    αν δεν το θες, www.antallakseto.gr
    Δημοσίευση στην κατηγορία: , , ,
  •  08-02-2011, 20:08 63124 σε απάντηση της 63081

    Απ: WPF MVVM - View Model Properties called more than once

    Αν βλέπω καλά: το IsValid καλείται από το CanSaveItem που είναι το CanExecute delegate για το command SaveItem.

    Οπότε βλέπω πολύ λογικό o getter του IsValid να καλείται συνεχώς αφού στο WPF τα commands γίνονται enabled/disabled αυτόματα μέσω του CommandManager. O ClassManager παρακολουθεί τις αλλαγές στο UI και αυτομάτως καλεί το CanExecute delegate για όσα commands έχουν.

    Τώρα, (εφόσον πάλι αν βλέπω καλά) η μοναδική περίπτωση για να πάρει τιμή explicitly το IsValid είναι μέσα από το viewModel.Value.IsValid = !errors.Any(); προφανώς συνεχώς σου φέρνει τη default τιμή γιατί δεν έχει τρέξει ο ErrorChangedHandler.


     

     


    Vir prudens non contra ventum mingit
  •  09-02-2011, 09:52 63133 σε απάντηση της 63124

    Απ: WPF MVVM - View Model Properties called more than once

    Βρήκα το πρόβλημα που είχε και ενημερωνόταν το isValid συνέχεια.
    Έκανα τις αλλαγές που δείχνει ο κώδικας παρακάτω.
    Μέσα στο σχόλια είναι το λάθος.
    Είναι σαν να είχε δύο instances του DataContext. Το ένα instance ενημερωνόταν όσον αφορά το isValid ενώ το δεύτερο δεν ενημερωνόταν

    @KelMan ευχαριστώ πολύ για την απάντησή σου. 
    Το ErrorChangedHandler τρέχει χωρίς πρόβλημα. Το πρόβλημα ήταν μόνο στο property του isValid 
    public MainWindow() {
                InitializeComponent();
                InitializeValidaton();
            }
    
            void InitializeValidaton() {
                viewModel = new Lazy<MainWindowViewModel>(() => (MainWindowViewModel)this.DataContext);
                Validation.AddErrorHandler(this, ErrorChangedHandler);
            }
    
    //       void InitializeValidaton() {
    //            viewModel = new Lazy<MainWindowViewModel>();
    //            Validation.AddErrorHandler(this, ErrorChangedHandler);
    //       }

    αν δεν το θες, www.antallakseto.gr
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems