I'm trying to catch WPF using MVVM pattern, where my set of models is implementing ObservableCollection
as well as Entity Framework's DbContext
.
My project is quite simple movies database (looking for movies on disc, getting info about these movies from web, and so on...).
I will be glad for any notes and recommendations for what to change / avoid in future.
I am especially afraid about my concept of MovieSet
, which is "common implementation" of ObservableCollection
& DbContext
. Actually it works but I feel that it could be implemented in better way.
- Movie model
My model (Movie
) implements commonly presented class ObservableObject
and calls OnPropertyChanged(PropertyName)
for whatever its property change.
MovieSet collection
/// <summary> /// MoviesSet - ObservableCollection of Movies synchronized with DbSet of Movies /// </summary> public class MoviesSet : ObservableCollection<Movie> { public class MoviesContext : DbContext { public DbSet<Movie> Movies { get; set; } public MoviesContext() : base("MoviesContext") { // allows to recreate database if model changes Database.SetInitializer<MoviesContext> (new DropCreateDatabaseIfModelChanges<MoviesContext>()); // allows to use properly "AttachDBFilename" from Connection strings AppDomain.CurrentDomain.SetData("DataDirectory", System.IO.Directory.GetCurrentDirectory()); } } private MoviesContext context = new MoviesContext(); /// <summary> /// Save context to database /// </summary> public void SaveContext() { context.SaveChanges(); } /// <summary> /// Add new movie to context /// </summary> /// <param name="movie"></param> public void AddMovie(Movie movie) { if ((context.Movies.Where(x => x.OrigName == movie.OrigName)).FirstOrDefault<Movie>() == null) { Add(movie); if (!string.IsNullOrEmpty(movie.OrigName)) { context.Movies.Add(movie); context.Entry(movie).State = EntityState.Added; } } } /// <summary> /// Notify modification of Movie object /// </summary> /// <param name="movie"></param> public void NotifyMovieModified(Movie movie) { if ((context.Movies.Where(x => x.OrigName == movie.OrigName)).FirstOrDefault<Movie>() != null) { context.Entry(movie).State = EntityState.Modified; } else { context.Entry(movie).State = EntityState.Added; } } /// <summary> /// Delete movie from context /// </summary> /// <param name="movie"></param> public void DeleteMovie(Movie movie) { Remove(movie); context.Entry(movie).State = EntityState.Deleted; context.Movies.Remove(movie); } /// <summary> /// Load context from database /// </summary> public void LoadContext() { context.Movies.Load(); foreach (Movie movie in context.Movies) { Add(movie); } } }
ViewModel
/// <summary> /// Movie model /// </summary> public class ViewModel : Mvvm.ObservableObject { MovieFileProcessor fileProc; public ICommand FindMoviesCommand { get; set; } // find all movie files cmd public ICommand DownloadDataCommand { get; set; } // download all unknown movies data cmd public ICommand PlayVideoCommand { get; set; } // play selected movie cmd public ICommand VisitWebCommand { get; set; } // visit selected movie web page cmd public ICommand DeleteCommand { get; set; } // delete selected movie cmd public ICommand CopyCommand { get; set; } // copy selected movie cmd public ICommand FormatFileNameCommand { get; set; } // format selected movie files cmd // observable and serializable set of Movies public MoviesSet Movies { get; set; } // actually selected movie obj private object selectedMovie; public object SelectedMovie { get { return selectedMovie; } set { if (selectedMovie != value) { selectedMovie = value; OnPropertyChanged("SelectedMovie"); } } } // application configuration private NameValueCollection appCfg = ConfigurationManager.GetSection("appCfg") as NameValueCollection; public ViewModel() { FindMoviesCommand = new Mvvm.RelayCommand(FindMovieFiles); DownloadDataCommand = new Mvvm.RelayCommand(DownloadMoviesData); PlayVideoCommand = new Mvvm.RelayCommand(PlayVideo); VisitWebCommand = new Mvvm.RelayCommand(VisitWeb); DeleteCommand = new Mvvm.RelayCommand(DeleteMovie); CopyCommand = new Mvvm.RelayCommand(CopyMovie); FormatFileNameCommand = new Mvvm.RelayCommand(FormatFileName); fileProc = new MovieFileProcessor(appCfg["MoviesDir"]); Movies = new MoviesSet(); // load Movies context from database Movies.LoadContext(); } /// <summary> /// Delete selected movie /// </summary> /// <param name="obj">SelectedMovie</param> private void DeleteMovie(object obj) { try { fileProc.Delete(obj as Movie); } catch { } Movies.DeleteMovie(obj as Movie); Movies.SaveContext(); } /// <summary> /// Copy movie to another directory /// </summary> /// <param name="obj">SelectedMovie</param> private void CopyMovie(object obj) { throw new NotImplementedException(); } /// <summary> /// Rename selected movie video file (& subtitles file, & subdirectory) /// </summary> /// <param name="obj">SelectedMovie</param> private void FormatFileName(object obj) { fileProc.Rename(obj as Movie); Movies.NotifyMovieModified(obj as Movie); Movies.SaveContext(); } /// <summary> /// Visit web page of selected movie /// </summary> /// <param name="obj">SelectedMovie</param> private void VisitWeb(object obj) { System.Diagnostics.Process.Start((obj as Movie).WebPage); } /// <summary> /// Play selected movie /// </summary> /// <param name="obj">SelectedMovie</param> private void PlayVideo(object obj) { Process.Start(appCfg["VideoPlayerPath"], (obj as Movie).VideoFile.ToString() + " -f"); } /// <summary> /// Find movie files in movies directory /// </summary> /// <param name="obj">Not used</param> public void FindMovieFiles(object obj) { foreach (Movie movie in fileProc.FindMovies()) { Movies.AddMovie(movie); } // Movies context not saved because OrigName (Primary Key) is empty at this moment } /// <summary> /// Download data from repository /// </summary> /// <param name="obj">Not used</param> public void DownloadMoviesData(object obj) { CsfdDataMiner miner = new CsfdDataMiner(); new Task(() => { foreach (Movie movie in Movies.Where(x => string.IsNullOrEmpty(x.OrigName))) { movie.WebPage = miner.GetMoviePage(movie.VideoFile.Name); miner.GetMovieData(movie); Movies.NotifyMovieModified(movie); } Movies.SaveContext(); }).Start(); } }
View - for completness
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0" x:Name="gridMovies" AutoGenerateColumns="False" Padding="10" CanUserAddRows="False" SelectionUnit="FullRow" SelectionMode="Single"
PreviewKeyDown="gridMovies_PreviewKeyDown" CanUserDeleteRows="False" IsReadOnly="True"
ItemsSource="{Binding Movies}" SelectedItem="{Binding SelectedMovie}">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<!--Disable highlight of current selected cell-->
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding VideoFile}" Header="VideoFile" />
<DataGridTextColumn Binding="{Binding HasSubtitles}" Header="Subtitles" />
<DataGridTextColumn Binding="{Binding OrigName}" Header="Name" />
<DataGridTextColumn Binding="{Binding LocalName}" Header="Local Name" />
<DataGridTextColumn Binding="{Binding Year}" Header="Year" />
<DataGridTextColumn Binding="{Binding CountriesStrings}" Header="Countries" />
<DataGridTextColumn Binding="{Binding GenresStrings}" Header="Genres" />
<DataGridTextColumn Binding="{Binding Rating}" Header="Rating" />
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu IsEnabled="{Binding Movies.Count}">
<MenuItem Command="{Binding PlayVideoCommand}" CommandParameter="{Binding SelectedMovie}" Header="Play Video"/>
<MenuItem Command="{Binding VisitWebCommand}" CommandParameter="{Binding SelectedMovie}" Header="Visit web"/>
<MenuItem Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedMovie}" Header="Delete movie"/>
<MenuItem Command="{Binding FormatFileNameCommand}" CommandParameter="{Binding SelectedMovie}" Header="Format file names"/>
<MenuItem Command="{Binding CopyCommand}" CommandParameter="{Binding SelectedMovie}" Header="Copy movie"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
<Grid Grid.Row="2" >
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Name="btnFindMovies" Content="Find Movies" Command="{Binding FindMoviesCommand}"></Button>
<Button Grid.Column="2" Name="btnDwnldData" Content="Download Data" Command="{Binding DownloadDataCommand}"></Button>
</Grid>
</Grid>