3
\$\begingroup\$

For this project I'm targeting .NET 4.6 with Prism 6 as a toolkit to implement correct MVVM patterns.

The code below allows me to raise a dialog box with Yes, No, Cancel buttons. If the user clicks 'Yes' the content is saved then the tab item closed. If 'No' the tab item is only closed, and if 'Cancelled' then return, do not close anything.

However the dialog is only displayed if the content is dirty. If not just close it.

I feel like in its current sate it's very messy and could be cleaned up. Take a look below and tell me what you think.

internal class CloseTabAction : TriggerAction<Button>
{
    protected override void Invoke(object parameter)
    {
        var args = parameter as RoutedEventArgs;
        if (args == null) return;

        var tabItem = FindParent<TabItem>(args.OriginalSource as DependencyObject);
        if (tabItem == null) return;

        var tabControl = FindParent<TabControl>(tabItem);
        if (tabControl == null) return;

        var region = RegionManager.GetObservableRegion(tabControl).Value;
        if (region == null) return;

        var context = new NavigationContext(region.NavigationService, null);

        if (Implements<IRequestTabItemClose>(tabItem.Content, r => r.IsContentDirty(isDirty =>
        {
            if (isDirty)
            {
                if (Implements<IRequestTabItemClose>(tabItem.Content,
                    i => i.ConfirmCloseRequest((canSave, cancel) =>
                    {
                        if (cancel) return;
                        if (!region.Views.Contains(tabItem.Content)) return;
                        if (canSave)
                        {
                            i.SaveContent();
                            region.Remove(tabItem.Content);
                            return;
                        }
                        region.Remove(tabItem.Content);
                    })))
                    return;
            }
            region.Remove(tabItem.Content);
        }))) return;
    }

    private static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        while (true)
        {
            var parentObject = VisualTreeHelper.GetParent(child);

            if (parentObject == null) return null;

            var parent = parentObject as T;
            if (parent != null) return parent;

            child = parentObject;
        }
    }

    private static T Implementor<T>(object content) where T : class
    {
        T implementor = content as T;
        if (implementor != null) return implementor;

        var element = content as FrameworkElement;
        if (element != null) implementor = element.DataContext as T;
        return implementor;
    }

    private static bool Implements<T>(object content, Action<T> action) where T : class
    {
        T target = Implementor<T>(content);
        if (target == null) return false;
        action(target);
        return true;
    }
}

Here are my custom interfaces:

public interface IRequestTabItemClose
{
    void ConfirmCloseRequest(Action<bool, bool> callback);
    void SaveContent();
    void IsContentDirty(Action<bool> callBack);
}

public interface ICloseConfirmation : INotification
{
    bool Confirmed { get; set; }
    bool Cancel { get; set; }
}

ViewModel:

    public bool IsDirty { get; set; }

    public virtual void ConfirmCloseRequest(Action<bool, bool> callback)
    {
        ClosePopupRequest.Raise(
            new CloseConfirmation {Title = "Confirm", Content = "Do you want to save this view before closing?"},
            r => { callback(r.Confirmed, r.Cancel); });
    }


    public virtual void SaveContent()
    {
        // Save stuff
    }

    public void IsContentDirty(Action<bool> callBack)
    {
        callBack(IsDirty);
    } 
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I've cleaned up my CloseTabAction class see below:

I took out the method 'Implements' because the interface is attached to my BaseViewModel so I know that every VM will have it, no need to check.

I created a new method to check if the content is dirty. Rather than putting it in my Invoke method. From there I raise my dialog and get the results back.

It still may need some work, but it's much better now.

internal class CloseTabAction : TriggerAction<Button>
{
    protected override void Invoke(object parameter)
    {
        var args = parameter as RoutedEventArgs;
        if (args == null) return;

        var tabItem = FindParent<TabItem>(args.OriginalSource as DependencyObject);
        if (tabItem == null) return;

        var tabControl = FindParent<TabControl>(tabItem);
        if (tabControl == null) return;

        var region = RegionManager.GetObservableRegion(tabControl).Value;
        if (region == null) return;

        var content = Implementor<IRequestTabItemClose>(tabItem.Content);

        if (IsDirty(content))
        {
            content?.ConfirmCloseRequest((canSave, cancel) =>
            {
                if (cancel) return;
                if (canSave)
                {
                    content.SaveContent();
                    region.Remove(tabItem.Content);
                    return;
                }
                region.Remove(tabItem.Content);
            });
        }
        else
        {
            region.Remove(tabItem.Content);
        }
    }

    private static bool IsDirty(IRequestTabItemClose content)
    {
        var isContentDirty = false;

        content?.IsContentDirty(isDirty =>
        {
            if (isDirty)
                isContentDirty = true;
        });
        return isContentDirty;
    }

    private static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        while (true)
        {
            var parentObject = VisualTreeHelper.GetParent(child);

            if (parentObject == null) return null;

            var parent = parentObject as T;
            if (parent != null) return parent;

            child = parentObject;
        }
    }

    private static T Implementor<T>(object content) where T : class
    {
        T implementor = content as T;
        if (implementor != null) return implementor;

        var element = content as FrameworkElement;
        if (element != null) implementor = element.DataContext as T;
        return implementor;
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.