This was the question asked in an interview.
There is a Label with a property Text
In one page a label is simple Label, in other pages it may handle any one or combination of the below actions
Clickable
Resizable
Draggable
How do you design this label component that applies OOP design Principle & Design Pattern?
I said that I would create the following:
public class Label
{
public string Text{get;set;}
}
public interface IClickable
{
void Click();
}
public interface IDraggable
{
void Drag();
}
public interface IResizable
{
void Resize();
}
So that if the client want Resizable Label
public class ResizableLabel:Label,IResizable
{
....
}
same way ClickableLable, DraggableLabel
However, I feel that this is the incorrect approach, because I do not want to add those concrete classes. I want to avoid having ClickableAndDraggableLabel or ClickableDraggableResizableLabel.
Is there any design pattern that would solve this problem without adding these concrete classes?
I would use Decorator pattern. It is used extensivelly in .net world for different kind of streams, that allow you to compose encrypted, zipped, text stream wrappers for byte stream, for example. class diagram is taken from wiki
Example for you situation is not so trivial in implementation, but usage doen't require another classes for new compising behavior:
// Define other methods and classes here
public class Label
{
public string Text{get;set;}
public virtual void MouseOver(object sender, EventArgs args) { /*some logic*/ }
public virtual void Click(object sender, EventArgs args) { /*some logic*/ }
//other low level events
}
public class ClikableLabel : Label
{
private Label _label;
public ClikableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class DraggableLabel : Label
{
private Label _label;
public DraggableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class ResizableLabel : Label
{
private Label _label;
public ResizableLabel(Label label)
{
_label = label;
}
public override void MouseOver(object sender, EventArgs args)
{
//specific logic
_label.MouseOver(sender, args);
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
now you can
var clickableDragableLabel = new ClikableLabel(new DraggableLabel(new Label{Text = "write me!"}));
var makeItResizable = new ResizableLabel(clickableDragableLabel);
I don't think Interface can resolve your problem.
I would make something more like this:
First, define an enum which list all your action:
public Enum LabelAction{ None = 0, Clickable = 1, Resizable = 2, Draggable = 4 }
For having multiple Enum defined, you can look this links:
How do you pass multiple enum values in C#?
Enumeration Types as Bit Flags
Then define a member in your class Label, taking an action:
public class Label
{
private readonly LabelAction _action;
private string Text { get; set; }
public class Label(string text)
: Label(text, LabelAction.None) { }
public class Label(string text, LabelAction action)
{
this.Text = text;
this._action = action;
}
public bool CanClick
{
get
{
return this._action & LabelAction.Clickable == LabelAction.Clickable;
}
}
public bool CanResize { get { return this._action & LabelAction.Resizable == LabelAction.Resizable ;} }
public bool CanDrag { get { return this._action & LabelAction.Draggable == LabelAction.Draggable ;} }
public Click()
{
if(this.CanClick) { /* click */ }
else { throw new Exception("Not clickable");}
}
public Drag()
{
if(this.CanDrag) { /* drag */ }
else { throw new Exception("Not draggable");}
}
public Resize()
{
if(this.CanResize) { /* resize */}
else { throw new Exception("Not resizable");}
}
}
Usage:
var simpleLabel = new Label("simple");
var clickable = new Label("clickable", LabelAction.Clickable);
var clickableDraggable = new Label("clickable and draggable", LabelAction.Clickable | LabelAction.Draggable);
public void DoEvent(Label label)
{
if(label.CanClick) label.Click();
if(label.CanDrag) label.Drag();
if(label.CanResize) label.Resize();
}
If you need to add an action, you will have to add one item to the Enum LabelAction, one method CanDo() and one method Do() to the Label class. Not so much so.
I would just have boolean properties for CanClick, drag, and resize, all default to true, and falsed as required (or as inherited).
constructor as follows
public Label(bool canClick = true, bool canDrag = true, bool canResize = true){}
Chances are if they're extending a class once, its going to be extended further at a later date
Well you can have a base class that implement all the interface and delegate their behaviour to concretes strategy classes.
Then you would have a NullDraggable, nulResizeable,NullClickable by default that do nothing (so your base label is not clickable, resizable and dragrable)
Then you create different strategy, like Clickable, DoubleClickable, WidthResizeable etc...
You then pass the combination you want to your class.
This way you obtain a lot of little strategy that are easy to reuse in other component with the same interface.
You can have multiple behaviour by using a composite pattern (for example you can have clickable and doubleclickable togheter)
This probably would be a little too ingeneered though
I think you're over-thinking what the interviewer must have had in his mind. If the case is as simple and practical, to avoid the complexity of over abstraction, then this would suffice:
public class Label
{
public string Text{get;set;}
}
public class ComlexLabel : Label
{
Click();
Drag();
Resize();
}
You can do any operation on it. Now if for a challenge you need only one concrete instance and need separate type of objects to be able to do only a combination of these things, its again simple - only that this time you have to create similar prototypes/interfaces:
public class Label
{
public string Text{get;set;}
}
public interface Clickable
{
Click();
}
public interface Resizable
{
Resize();
}
public interface Dragable
{
Drag();
}
public interface ClickableDragable : Clickable, Draggable
{
}
public interface ClickableResizable : Clickable, Resizable
{
}
public interface ResizableDragable : Resizable, Draggable
{
}
public interface ClickableDragableResizeable : Resizable, Clickable, Draggable
{
}
public class ComlexLabel : Lable, ClickableDragableResizeable
{
Click();
Drag();
Resize();
}
Now you can have instances of ComlexLabel by making the type that gives the required feature. Like:
ResizableDragable rd = new ComlexLabel();
ClickableResizable cr = new ComlexLabel();
ClickableDragableResizeable cdr = new ComlexLabel();
Now rd, cr and cdr have different capabilities. And only one concrete instance behind them. To prevent the clients from getting full privilege by doing
var cdr = new ComplexLabel();
you should make the ComplexLabel constructor private and assign the task to some factory. Like
var rd = Factory.GetResizableDragableLabel();
Now rd must be just ResizableDragable with no Click functionality..
I think for this scenario, there is no need to re-invent the wheel. Even though the question explicitly asks for OOP it is not explicitly asking you to ignore Component Model programming nor event based behaviors.
That's why I would follow an approach that allows a division of responsibilities where Label is responsible to notify when it is being clicked or dragged and SomeOtherComponent might or might not listen to such notification (event) in order to perform other logic.
Please take a look at the links below for examples of the approach of event dispatching for those user actions :
Drag and Drop
Label Class
Regards,
Related
I'm making user changeable settings for my media player and I'm struggling to find an elegant solution to the problem.
One of my settings for example - pauses the video at it's last frame, if not checked it will either continue through the playlist or if it's only 1 file, reset it and pause it at the start.
This is how I've implemented it:
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This is contained inside the ViewModel of the main window, where the media element is and GeneralSettings.PauseOnLastFrame is a boolean property.
This command is binded as follows:
<MediaElement ....>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="MediaEnded">
<ia:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
</ia:EventTrigger>
</ia:Interaction.Triggers>
</MediaElement>
It works but it's awful, how should I go about implementing such setting system in an elegant way? Some settings might not be boolean, they might have multiple options, some might be applied only on startup and others, as the one illustrated above, event based.
Based on the information and sample code you provided, I would suggest
Approach - 1
A tightly couple ViewModel with System.Configuration.ApplicationSettingsBase and you can mention all you properties in ViewModel and map single of them with a separate application setting property. You can use your settings directly in biding afterwards e.g. : {x:Static Settings.Default.Whatevs}. Othe "Save" button click event or main window close event, you can save all you settings e.g. : Settings.Default.Save();
Approach - 2
A better approach, I would suggest / prefer (if I am developing this app) is to develop a wrapper class (e.g.: SettingProvider) that implement an inheritance (e.g: ISettingProvider) which uncovers all you settings as separate properties and also have a save method which saves all setting values. You can use this wrapper class into your ViewModel to handle all the commands and setting values in better way.
The benefit of this approach is the if you decide to change you setting to database , you need not to make change to you ViewModel as all job is done in SettingProvider class.
I am not sure but based on viewing your code, I assume that you used Approach-1. Please put you comments and any feedback to this answer. I would like to know what you think and may be you have got more simple and interesting way of achieving this.
UPDATE-1
Example
Enum for showing you demo
public enum MediaStatus
{
Playing = 0,
Stopped = 1,
Paused = 2
}
Interface
public interface ISettingProvider
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Wrapper Class
public class SettingProvider : ISettingProvider
{
private double volumne;
public double Volumne // read-write instance property
{
get
{
return volumne;
}
set
{
volumne = value;
Settings.Default.Volumne = volumne;
}
}
private string lastMediaUrl;
public string LastMediaUrl // read-write instance property
{
get
{
return lastMediaUrl;
}
set
{
lastMediaUrl = value;
Settings.Default.LastMediaUrl = lastMediaUrl;
}
}
private MediaStatus playingMediaStatus;
public MediaStatus PlayingMediaStatus // read-write instance property
{
get
{
return playingMediaStatus;
}
set
{
playingMediaStatus = value;
Settings.Default.PlayingMediaStatus = (int)playingMediaStatus;
}
}
public void SaveSettings()
{
Settings.Default.Save();
}
//Constructor
public SettingProvider()
{
this.Volumne = Settings.Default.Volumne;
this.LastMediaUrl = Settings.Default.LastMediaUrl;
this.PlayingMediaStatus = (MediaStatus)Settings.Default.PlayingMediaStatus;
}
}
ViewModelBase Class
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
CommandHandler Class
public class CommandHandler : ICommand
{
public event EventHandler CanExecuteChanged { add { } remove { } }
private Action<object> action;
private bool canExecute;
public CommandHandler(Action<object> action, bool canExecute)
{
this.action = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute;
}
public void Execute(object parameter)
{
action(parameter);
}
}
ViewModel
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
public double Volumne
{
get
{
return objSettingProvider.Volumne;
}
set
{
objSettingProvider.Volumne = value;
OnPropertyChanged("Volumne");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand saveSettingButtonCommand;
public ICommand SaveSettingButtonCommand
{
get
{
return saveSettingButtonCommand ?? (saveSettingButtonCommand = new CommandHandler(param => saveSettings(param), true));
}
}
private void saveSettings()
{
objSettingProvider.SaveSettings();
}
}
UPDATE-2
public interface ISettingProvider
{
bool PauseOnLastFrame;
bool IsAutoPlay;
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
public class SettingProvider : ISettingProvider
{
private bool pauseOnLastFrame;
public bool PauseOnLastFrame // read-write instance property
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
Settings.Default.PauseOnLastFrame = volumne;
}
}
private bool isAutoPlay;
public bool IsAutoPlay // read-write instance property
{
get
{
return isAutoPlay;
}
set
{
isAutoPlay = value;
Settings.Default.IsAutoPlay = volumne;
}
}
}
public class SettingsViewModel : ViewModelBase
{
SettingProvider objSettingProvider = new SettingProvider();
MediaStatus PlayingMediaStatus
{
get
{
return objSettingProvider.PlayingMediaStatus;
}
set
{
if(value == MediaStatus.Paused)
MediaPlayer.Pause();
if(value == MediaStatus.Playing)
MediaPlayer.Play();
if(value == MediaStatus.Stopped)
MediaPlayer.Stop();
objSettingProvider.PlayingMediaStatus = (int)value;
OnPropertyChanged("PlayingMediaStatus");
}
}
private string currentMediaFile;
public string CurrentMediaFile
{
get
{
return currentMediaFile;
}
set
{
currentMediaFile = value;
MediaPlayer.Stop();
MediaPlayer.Current = currentMediaFile;
if(objSettingProvider.IsAutoPlay)
MediaPlayer.Play();
OnPropertyChanged("CurrentMediaFile");
}
}
// Implementaion of other properties of SettingProvider with your ViewModel properties;
private ICommand onMediaEndedCommand;
public ICommand OnMediaEndedCommand
{
get
{
return onMediaEndedCommand ?? (onMediaEndedCommand = new CommandHandler(param => onMediaEnded(param), true));
}
}
private void onMediaEnded()
{
if(objSettingProvider.PauseOnLastFrame)
{
PlayingMediaStatus = MediaStatus.Paused;
}
else if(PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
PlayingMediaStatus = MediaStatus.Stopped;
}
else
{
CurrentMediaFile = PlayListViewModel.FilesCollection.MoveNext();
}
}
}
NOTE: This is the detailed example I put here and also avoid some syntax error or naming error if I missed somewhere. Please correct it.
I am not aware which media player settings you are using. I took some sample properties. This is just an example of structure you can implement for you application. You may need to alter more code to implement this structure.
An elegant way to implement this IMHO would be to use a dependency injection container, this will provide great flexibility while allowing you to completely separate concerns (i.e. the settings implementation from your view models and custom controls).
There are many DI frameworks out there, for my example I will use simple injector because it is free (open source), simple and fast but you can apply the same principle to other frameworks (Unity, Ninject, etc..).
Start by creating an interface for your settings service, for example:
public interface ISettingsService
{
double Volumne { get; set; }
string LastMediaUrl { get; set; }
MediaStatus PlayingMediaStatus;
void SaveSettings();
}
Then add your implementation for the service, the beauty of using DI is that you can change the implementation at anytime or completely replace it and your application will continue to work as usual.
Let's say you want to use application settings, here is your service:
public class SettingsServiceFromApplication : ISettingsService
{
public double Volume
{
get
{
return Properties.Settings.Volume;
}
}
[...]
}
Or let's say you want to use a database to store your settings:
public class SettingsServiceFromDb : ISettingsService
{
public double Volume
{
get
{
return MyDb.Volumen;
}
}
[...]
}
Then you can use a DI container to specify which implementation to use:
Start by installing the library using NuGet:
Install-Package SimpleInjector -Version 4.0.12
You need a way to share your container throughout the application, I usually just go with a static class that I initialize when starting the app:
using Container = SimpleInjector.Container;
namespace YourNamespace
{
public class Bootstrapper
{
internal static Container Container;
public static void Setup()
{
//Create container and register services
Container = new Container();
//Let's specify that we want to use SettingsServiceFromApplication
Container.Register<ISettingsService, SettingsServiceFromApplication>();
//You can use your bootstrapper class to initialize other stuff
}
}
You need to call Setup when starting the App, the best place is in the App constructor:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper.Setup();
}
}
So now you have an app wide depedency injection container that you can use to request "services" (specific implementations of an interface).
To get the settings implementation in your view models you could simply call the container as follows:
// This will return an instance of SettingsServiceFromApplication
ISettingsService settingsService = Bootstrapper.Container.GetInstance<ISettingsService>();
double volumen = settingsService.Volume;
To make it easier to work with, I usually create a base view model that will allow to get services more easyly, for example:
public abstract BaseViewModel
{
private ISettingsService _settings;
protected ISettingsService GeneralSettings
{
get
{
if (_settings == null)
_settings = Bootstrapper.Container.GetInstance<ISettingsService>();
return _settings;
}
}
}
Every view model inheriting from this class will have access to the settings:
public class YourViewModel : BaseViewModel
{
private void OnMediaEndedCommand()
{
if (GeneralSettings.PauseOnLastFrame)
{
MediaPlayer.SetMediaState(MediaPlayerStates.Pause);
return;
}
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
}
As you can see the code is the same as your code! But now the settings are coming from your container. Where is the elegance? Well, let's say that one year from now someone decides that you will store your settings in a database, what do you need to change in your code?
Container.Register<ISettingsService, SettingsServiceFromDb>();
A single line. Everything else should work as usual.
As well as view models, you could use this mechanism in your own controls:
public class MyMediaElement : UserControl //Or MediaElement and instead of commands you can override real events in the control code behind, this does not break the MVVM pattern at all, just make sure you use depedency properties if you need to exchange data with your view models
{
private void OnMediaEndedCommand()
{
//Get your settings from your container, do whatever you want to do depending on the settings
[...]
}
}
Then just use your control in your Views / ViewModels:
<local:MyMediaElement />
Yep, that's all you need because you handle everything in your User / Custom control, your view models doesn't need to care about how you handle settings in the control.
There are many options you can use to register containers, I recommend you take a look at the docs:
https://simpleinjector.org/index.html
https://simpleinjector.readthedocs.io/en/latest/index.html
I think maybe you are looking for an interface approach?
public interface IMediaEndedHandler
{
bool AlternateHandling(MediaPlayer player);
}
public class NullMediaEndedHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
return false;
}
}
public class PauseOnLastFrameHandler : IMediaEndedHandler
{
public bool AlternateHandling(MediaPlayer player)
{
player.SetMediaState(MediaPlayerStates.Pause);
return true;
}
}
public class GeneralSettings
{
private bool pauseOnLastFrame;
private bool PauseOnLastFrame
{
get
{
return pauseOnLastFrame;
}
set
{
pauseOnLastFrame = value;
MediaEndedHandler = value
? new PauseOnLastFrameHandler()
: new NullMediaEndedHandler();
}
}
public IMediaEndedHandler MediaEndedHandler = new NullMediaEndedHandler();
}
Then:
private void OnMediaEndedCommand()
{
if (GeneralSettings.MediaEndedHandler.AlternateHandling(MediaPlayer))
return;
if (PlayListViewModel.FilesCollection.Last().Equals(PlayListViewModel.FilesCollection.Current) && !Repeat)
{
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
MediaPlayer.SetMediaState(MediaPlayerStates.Stop);
return;
}
ChangeMediaPlayerSource(PlayListViewModel.ChangeCurrent(() => PlayListViewModel.FilesCollection.MoveNext()));
}
This way, if your setting is, for example. an enum instead of a bool, you can specify a different implementation of the interface for each possible value.
I'm porting to Xamarin.IOS a swift library that makes some material design animation with UIButton.
The swift counterpart is a subclass of UIButton and overrides layoutSublayersOfLayer.
Swift:
public override func layoutSublayersOfLayer(layer: CALayer) {
super.layoutSublayersOfLayer(layer)
if self.layer == layer {
layoutShape()
layoutVisualLayer()
}
}
On Xamarin side I noted that this method is called through CALayerDelegate, which is associated with CALayer through de property Delegate.
I tried to subclass CALayerDelegate and replace de Delegate property, but when I did that the button didn't rendered correctly and stopped respond to events.
Is there a way to override layoutSublayersOfLayer on Xamarin.Ios ? Is there another method that I can override to prepare stuff before a particular layer is drawn ?
public class MaterialButtonLayerDelegate : CALayerDelegate
{
private readonly MaterialButton _button;
public MaterialButtonLayerDelegate(MaterialButton button):base()
{
_button = button;
}
public override void LayoutSublayersOfLayer(CALayer layer)
{
if (_button.Layer == layer)
{
_button.LayoutShape();
_button.LayoutVisualLayer();
}
}
}
[Register("MaterialButton")]
public class MaterialButton : UIButton
{
public CAShapeLayer VisualLayer { get; private set; } = new CAShapeLayer();
public MaterialButton(CGRect frame):base(frame)
{
PrepareView();
}
protected void PrepareView()
{
Layer.Delegate = new MaterialButtonLayerDelegate(this);
ContentScaleFactor = MaterialDevice.Scale();
PrepareVisualLayer();
}
protected virtual void PrepareVisualLayer()
{
VisualLayer.ZPosition = 0;
VisualLayer.MasksToBounds = true;
Layer.AddSublayer(VisualLayer);
}
protected virtual void LayoutShape()
{
//...
}
protected virtual void LayoutVisualLayer()
{
//...
}
}
Thanks!
The only possible way to do it is extending CALayerDelegate class and setting it as your CAShapeLayer delegate. Be sure to call delegate's base members wherever possible as it performs required bindings behind the scenes.
public override void LayoutSublayersOfLayer(CALayer layer)
{
base.LayoutSublayersOfLayer(layer);
if (_button.Layer == layer)
{
_button.LayoutShape();
_button.LayoutVisualLayer();
}
}
We are using caliburn.micro for one of our projects and I'm currently having a puzzling problem:
we have the following classes:
ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
DocViewerViewModel : Conductor<DocumentViewModel>
and various document-views, all with this base class:
DocumentViewModel : Screen
The ToolViewerViewModel is to manage multiple dock-able tool views which allow the user to control different aspects of the program.
The DocViewerViewModel is to show the user the data he's working on/with. It is here to present one of the many DocumentViewModel to the user. and is implemented as a special dock-able view which can not be closed or detached from the ToolViewerView. For every aspect of the data a specific DocumentViewModel is generated by the DocViewerViewModel and presented to the user.
The DocumentViewModel is the base class for all presentation aspects of the data. One may present the data as a table an other may present it as a chart, and so on...
We now encounter problems in terms of OnActivate() and OnDeactivate() which are not called when we expect them to be called.
First Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the DocViewerViewModel. When the user now selects one of the other dock-able views the OnDeactivate() method from the DocumentViewModel is being called. Which makes absolutely no sense to me. I'd expect the DocViewerViewModel.OnDeactivate() to be called.
Second Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the view that enables the user to change the DocumentViewModel presented by the DocViewerViewModel. When the user now selects an other DocumentViewModel the following code is being executed within the DocViewerViewModel:
DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true);
DocViewerViewModel.ActivateItem(new DocumentViewModel());
I'd expect the DocumentViewModel.OnDeactivate() to be called upon the DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true) call. but that never happens.
Conclusion:
The only proper working Conductor is the ToolViewerViewModel which is managing everything. But this behavior is not what we want or expect to happen: We'd like to have the ToolViewerViewModel only Conduct the dock-able views and the DocViewerViewModel to conduct the DocumentViewModel. This is important because there are two different use cases in place: One to manage multiple instances at the same time and the other where only one instance is active and used, the old instance shall be thrown away.
Hopefully anyone here can help me to get the behavior I'm looking for.
I Now have an example code for you:
public class ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly IDockManager _dockManager;
private readonly DocViewerViewModel _docViewerViewModel;
private readonly IList<DockableViewModel> _toolViews = new List<DockableViewModel>();
public ToolViewerViewModel(IViewModelFactory viewModelFactory, DocViewerViewModel docViewerViewModel, IDockManager dockManager)
{
_dockManager = dockManager;
_viewModelFactory = viewModelFactory;
_docViewerViewModel = docViewerViewModel;
}
protected override void OnViewLoaded(object view)
{
_dockManager.Link(this);
_dockManager.CreateSpecialPaneFor(_docViewerViewModel);
ActivateItem(_docViewerViewModel);
ShowToolView<ProjectExplorerViewModel>();
base.OnViewLoaded(view);
}
public void ShowToolView<T>() where T : DockableViewModel
{
if (!IsToolViewOpen<T>())
{
var viewModel = _viewModelFactory.Create<T>();
ActivateItem(viewModel);
RefreshMenu(typeof(T));
}
}
}
Next class:
public class DocViewerViewModel : Conductor<DocumentViewModel>
{
private readonly IViewModelFactory _viewModelFactory;
public DocViewerViewModel(IViewModelFactory viewModelFactory)
{
_viewModelFactory = viewModelFactory;
}
public bool ShowInMainView<T>() where T : DocumentViewModel
{
return ShowInMainView(typeof(T));
}
private bool ShowInMainView(Type viewModelType)
{
var ret = false;
// close the current view
if (ActiveItem != null)
{
DeactivateItem(ActiveItem, true); //The close flag is on true since we want to remove the current instance from the memory
}
// check whether the current viewModel has been closed successfully
if (ActiveItem == null)
{
try
{
var viewModel = _viewModelFactory.Create(viewModelType) as DocumentViewModel;
if (viewModel != null)
{
ActivateItem(viewModel);
ret = true;
}
else
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
catch (Exception ex)
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
return ret;
}
}
and the last one:
public abstract class DocumentViewModel : Screen
{
private bool _isDirty;
protected IViewModelFactory ViewModelFactory { get; private set; }
protected IEventAggregator EventAggregator { get; private set; }
public bool IsDirty
{
get
{
return _isDirty;
}
protected set
{
if (value.Equals(_isDirty))
{
return;
}
_isDirty = value;
NotifyOfPropertyChange(() => IsDirty);
}
}
protected DocumentViewModel(IViewModelFactory viewModelFactory, IEventAggregator eventAggregator)
{
ViewModelFactory = viewModelFactory;
EventAggregator = eventAggregator;
}
protected override void OnDeactivate(bool close)
{
if (close)
{
if (EventAggregator != null)
{
EventAggregator.Unsubscribe(this);
}
}
base.OnDeactivate(close);
}
protected override void OnActivate()
{
if (EventAggregator != null)
{
EventAggregator.Subscribe(this);
}
base.OnActivate();
}
public override void CanClose(Action<bool> callback)
{
var ret = true;
if (IsDirty && (ViewModelFactory != null))
{
var saveDialog = ViewModelFactory.Create<SaveDialogViewModel>();
saveDialog.Show();
if (saveDialog.DialogResult == DialogResult.Cancel)
{
ret = false;
}
else
{
if (saveDialog.DialogResult == DialogResult.Yes)
{
Save();
}
else
{
Discard();
}
IsDirty = false;
}
}
callback(ret);
}
public abstract void Save();
public virtual void Discard()
{
}
}
With this code the only time the DocumentViewModel.OnDeactivate() is being called when the user brings an other dock-able view into focus while the DocViewerViewModel was having the focus. This should not happen!
When the user is changing the focus between the dock-able views the DocumentViewModel.OnDeactivate() should not get call. But it must get called when ever the Method DocViewerViewModel.ShowInMainView<SomeDocumentViewModel>() is being called. Which isn't the case currently.
As far as I can tell, there is nothing wrong with the way your code is written. Since you are using MVVM, I suggest you design a test case like I've provided here.
And here's a snippet of the test case
// TestHarness.cs
[TestMethod]
public void CheckDeactivation()
{
// We'd like to have the ToolViewerViewModel only Conduct the dock-able views
// and the DocViewerViewModel to conduct the DocumentViewModel.
IViewModelFactory factory = new ViewModelFactory();
DocViewerViewModel docViewer = new DocViewerViewModel(factory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(factory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
DocumentViewModel docView1 = new NoDataViewModel();
DocumentViewModel docView2 = new NoDataViewModel();
docViewer.ActivateItem(docView1);
docViewer.ActivateItem(docView2);
Assert.AreEqual(0, docViewer.CountDeactivated());
}
I have had the exact same problem as you, and ended up using PropertyChangedBase instead of Screen and got the problem to disappear.
Later, after reading the docs on Screens and Conductors here, I realized that I wasn't activating the conductor itself further up in the view hierarchy!
So have a look at wherever you use your ToolViewerViewModel, and make sure you activate that instance!
Thank you very much for your Test. Even thought it is really nice code it tests the wrong code part. Your code simply tests whether the Method ActivateItem() or DeactivateItem() is being called:
public override void ActivateItem(DocumentViewModel item)
{
_countActivated++;
base.ActivateItem(item);
}
public override void DeactivateItem(DocumentViewModel item, bool close)
{
_countDeactivated++;
base.DeactivateItem(item, close);
}
But since these Methods are being called explicitly we don't need to test for that...
The real Problem is that the Conductor is not calling the OnActivate() or OnDeactivate() on the DocumentViewModel class. To enhance your test I used the following code:
public class DummyViewModelFactory : IViewModelFactory
{
private readonly Dictionary<Type, Func<object>> _registredCreators = new Dictionary<Type, Func<object>>();
public T Create<T>() where T : PropertyChangedBase
{
return Create(typeof(T)) as T;
}
public object Create(Type type)
{
if (type == null)
{
return null;
}
if (_registredCreators.ContainsKey(type))
{
return _registredCreators[type]();
}
return null;
}
public void Release(object instance)
{
}
public void RegisterCreatorFor<T>(Func<T> creatorFunction)
{
_registredCreators.Add(typeof(T), () => creatorFunction());
}
}
As concrete DocumentViewModel implementation I made:
public class NoDataViewModel : DocumentViewModel
{
public NoDataViewModel(IEventAggregator eventAggregator,
IViewModelFactory viewModelFactory)
: base(viewModelFactory, eventAggregator, )
{
}
public override void Save()
{
// nothing to do
}
public override void Reload()
{
// nothing to do
}
}
public class NoDataViewModelMock : NoDataViewModel
{
private static int activationCounterForTesting = 0;
private static int deactivationCounterForTesting = 0;
public static int ActivationCounterForTesting
{
get
{
return activationCounterForTesting;
}
}
public static int DeactivationCounterForTesting
{
get
{
return deactivationCounterForTesting;
}
}
public NoDataViewModelMock()
: base(null, null)
{
}
protected override void OnActivate()
{
activationCounterForTesting++;
base.OnActivate();
}
protected override void OnDeactivate(bool close)
{
deactivationCounterForTesting++;
base.OnDeactivate(close);
}
}
And I changed your Testmethod to this:
[TestMethod]
public void CheckDeactivation()
{
var viewModelFactory = new DummyViewModelFactory();
viewModelFactory.RegisterCreatorFor<NoDataViewModel>(() => new NoDataViewModelMock());
var docViewer = new DocViewerViewModel(viewModelFactory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(viewModelFactory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
Assert.AreEqual(3, NoDataViewModelMock.ActivationCounterForTesting);
Assert.AreEqual(2, NoDataViewModelMock.DeactivationCounterForTesting);
}
Then you'll see that the OnActivate() and OnDeactivate() methods are never been called.
With a little more advanced test you'd also see that they are being called but from the ToolViewerViewModel directly. I'd like to know why and how I can change this behavior to fit my needs:
The DocumentViewModel.OnActivate() method should get called when the DocViewerViewModel.ShowInMainView<T>() method gets called.
The DocumentViewModel.OnDeactivate() method should get called on the old DocumentViewModel when a new one is being shown by calling the DocViewerViewModel.ShowInMainView<T>()
Our Solution for that Problem is to remove the use Screen as BaseClass for DocViewerViewModel an implement the Conductor Logic our self.
I am creating a Custom control in where I am creating a property of the type "List"
Sections is a public class which has 4 properties.
The code in the control looks as follows:
public partial class genericGauge : Control
{
public genericGauge()
{
InitializeComponent();
}
// Stripped out code not needed for this issue question.
private List<Sections> indicators = new List<Sections>();
public List<Sections> Indicators
{
get
{
return indicators;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Stripped out code not needed for this issue question.
}
}
The Sections Class is as follows:
public class Sections
{
private string header = "Section1";
public string Header
{
get {return header;}
set
{
header = value;
}
}
private float startvalue = 0.0f;
public float StartValue
{
get { return startvalue; }
set
{
startvalue = value;
}
}
private float sweepvalue = 0.0f;
public float SweepValue
{
get { return sweepvalue; }
set
{
sweepvalue = value;
}
}
private Color sectioncolor = new Color();
public Color SectionColor
{
get {return sectioncolor;}
set
{
sectioncolor = value;
}
}
}
Everything seems to work fine except that when I add items to the collection at designtime using the property browsers typeeditor the control is not repainted to reflect what is added to the collection.
When I click outside the control on my testform it is repainted.
Usually with simple properties I would use Invalidate, but this seems not to be possible here.
I also tried with other collection types than List<> where it is allowed to have a set accessor, but Invalidate still wont be called. I assume that it means that the SET is never called.
I know how to get this to work with expandable properties but I have no luck finding how to make this update with collections.
I hope someoone can help me out.
thanks in advance.
Instead of using the class List, use the class ObservableCollection, and use that to get notified when a new section is added or removed from the list.
private ObservableCollection<Sections> indicators = new ObservableCollection<Sections>();
public IList<Sections> Indicators
{
get
{
return indicators;
}
}
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged;
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// possibly inspect the NotifyCollectionChangedEventArgs to see if it's a change that should cause a redraw.
// or not.
this.Invalidate();
}
When using your example exactly as it was the Indicators property was not available for editing in the property window. So I made a few changes to it.
I added a new class:
// Added this class to deal with the Sections class
public class SectionObservable : ObservableCollection<Sections>
{
// Added a few methods here for creating a designtime collection if I need to.
}
Then I made the change as you suggested
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged; // your suggestion
}
And made the property like this instead:
private SectionObservable indicators = new SectionObservable(); // using the SectionObservable class instead
public SectionObservable Indicators // using the SectionObservable class instead
{
get
{
return indicators;
}
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) // your suggestion
{
this.Invalidate();
}
And now works as a charm.
Thank you very much. I appreciate to see that it IS possible to get help this fast. I like this forum alot.
This is my first C# application, entirely self-taught without any prior software programming background. I did some research on Undo/Redo but could not find anything helpful (or easy to understand). Therefore, I'm hoping someone can help me in designing undo/redo function for my program (winforms application). The application consist of a main form where subsequent child forms will be called to record user specified values during certain events (button clicks etc). After every event is handled, a bitmap will be drawn in buffer and then loaded to a picturebox within the main form during the OnPaint event of the main form. Each input in separated into custom class objects and added into separate List and BindingList. Objects contained within List are used for graphics (to indicate coordinates etc) while objects in BindingList are used to display some important values on DataGridView. Just to give a brief description, the codes look something like this:
public partial class MainForm : form
{
public class DataClass_1
{
public double a { get; set; }
public double b { get; set; }
public SubDataClass_1 { get; set; }
}
public class SubDataClass_1
{
public double x { get; set; }
public double y { get; set; }
public string SomeString { get; set; }
public CustomEnum Enum_SubDataClass_1 { get; set; }
}
public class DisplayDataClass
{
public string SomeString { get; set; }
public double e { get; set; }
public double f { get; set; }
}
public enum CustomEnum { Enum1, Enum2, Enum3 };
// Lists that contain objects which hold the necessary values to be drawn and displayed
public List<DataClass_1> List_1 = new List<DataClass_1>();
public List<DataClass_2> List_2 = new List<DataClass_2>(); // Object has similar data types as DataClass_1
public BindingList<DisplayDataClass> DisplayList = new BindingList<DisplayDataClass>();
Bitmap buffer;
public MainForm()
{
InitializeComponent();
dgv.DataSource = DisplayList;
}
private void DrawObject_1()
{
// some drawing codes here
}
private void DrawObject_2()
{
// some drawing codes here
}
protected override void OnPaint(PaintEventArgs e)
{
DrawObject_1();
DrawObject_2();
pictureBox1.Image = buffer;
}
// Event to get input
private void action_button_click(object sender, EventArgs e)
{
ChildForm form = new ChildForm(this);
form.ShowDialog();
Invalidate();
}
}
The child forms' codes look something like this:
public partial class ChildForm : form
{
public ChildForm(MainForm MainForm)
{
InitializeComponent();
// Do something
}
private void ok_button_click(object sender, EventArgs e)
{
DataClass_1 Data_1 = new DataClass_1();
DisplayDataClass DisplayData = new DisplayDataClass();
// Parsing, calculations, set values to Data_1 and DisplayData
MainForm.List_1.Add(Data_1);
MainForm.DisplayList.Add(DisplayData);
this.DialogResult = System.Windows.Forms.DialogResult.OK;
this.Close();
}
}
Since all necessary data are stored in the lists and will only be changed after certain events are triggered (mostly button clicks), therefore I tried to use these lists to determine the state of the application during run time. My approach in implementing the undo/redo function is by adding the following codes:
public partial class MainForm : form
{
public class State()
{
public List<DataClass_1> List_1 { get; set; }
public List<DataClass_2> List_2 { get; set; }
public BindingList<DisplayDataClass> DisplayList { get; set; }
// and so on
public State()
{
List_1 = new List<DataClass_1>();
List_2 = new List<DataClass_2>();
DisplayList = new BindingList<DisplayDataClass>();
}
}
State currentState = new State();
Stack<State> undoStack = new Stack<State>();
Stack<State> redoStack = new Stack<State>();
private void MainForm_Shown(object sender, EventArgs e)
{
// Saves original state as first item in undoStack
undoStack.Push(currentState);
}
protected override void OnPaint(PaintEventArgs e)
{
// Update lists from currentState before drawing
List_1 = new List<DataClass_1>(currentState.List_1);
List_2 = new List<DataClass_2>(currentState.List_2);
DisplayList = new BindingList<DisplayDataClass>(currentState.DisplayList);
}
// When undo button is clicked
private void undo_button_Click(object sender, EventArgs e)
{
if (undoStack.Count > 0)
{
redoStack.Push(currentState);
undoStack.Pop();
currentState = undoStack.Last();
Invalidate();
}
}
// When redo button is clicked
private void redo_button_Click(object sender, EventArgs e)
{
// Have not thought about this yet, trying to get undo done first
}
// Events that trigger changes to values held by data objects
private void action_button_Click(object sender, EventArgs e)
{
// Replace the following code with previously stated version
if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ChildForm form = new ChildForm(this)
UpdateState();
undoStack.Push(currentState);
Invalidate();
}
}
// To update currentState to current values
private void UpdateState()
{
currentState.List_1 = new List<DataClass_1>(List_1);
currentState.List_2 = new List<DataClass_2>(List_2);
currentState.DisplayList = new BindingList<DisplayDataClass>(DisplayList);
// and so on
}
}
Result:
The application does not perform the undo function correctly. The program shows the correct output under normal conditions but when the undo event is triggered, regardless of how many objects have been drawn, the application reverts back to initial state (the state where there is no recorded data). I've used System.Diagnostics.Debug.WriteLine() during events where the stack is changed to check the number of counts within undoStack and it seems to give the correct counts. I'm guessing that the lists need to be copied/cloned in a different manner? Or am I doing something wrong here? Can anyone please guide me? Performance, readability, resource management, future maintenance and etc need not be considered.
There are a lot of approaches that will work, each with different strengths and weaknesses, but I generally like to define an abstract Action class and then a separate UndoRedoStack class.
The Action class would have two methods (Do and Undo) which each subclassed Action can implement. You isolate any logic that can "change state" to these Action subclasses thereby keeping that logic neatly encapsulated.
The UndoRedoStack is like a regular stack except with three core methods.
ApplyAction (like Push)
UndoAction (like Pop, but be sure to only
move the pointer/index without truncating or throwing away any
existing actions).
RedoAction (like Push, but you use the next value
already in the underlying stack/list instead of pushping/inserting a
new one).
Usually I find the biggest challenge then becomes designing each Action subclass in such a way that it maintains enough information to both undo and redo itself. But being able to encapsulate all state manipulation logic to individual Action subclasses usually makes it easiest for me to maintain in the long run.
You are storing reference objects in your stacks. If you want your method to work, you need to implement a clone() method in your state object, and store a new clone each time, otherwise, changes made are made to each member of the stack, as they all point to the same reference object.