Press "Enter" to skip to content

12 – MiniSqlAgent – Implementare il Job Manager per la Console- Modifiche a MiniSqlAgentConsole

In questo secondo post dedicato all’implementazione della User Interface per la gestione dei Job del nostro MiniSqlAgent, andremo a creare la porzione dell’interfaccia utente contenuta all’interno della finestra principale della console del servizio, per poi concludere con lo sviluppo della classe manager dei Job, in cui utilizzeremo lo User Control che abbiamo sviluppato nei post precedenti che potete leggere agli indirizzi qui sotto riportati.

Le modifiche alla classe MainWindow

In questo post, smontiamo le modifiche temporanee fatte per testare lo User Control per il singolo Job e inseriamo il necessario per Gestire il futuro Job Manager.

<MenuItem  
    Header="{x:Static p:Resources.mnuMWJobs}"
    VerticalAlignment="Center" Margin="10,2,10,2"
    Padding="10,4,10,4"  
    Height="32" >
    <MenuItem  
        Header="{x:Static p:Resources.mnuMWJobsManager}"      
        VerticalAlignment="Center"      
        Command="{x:Static lcmds:MiniSqlAgentCommands.OpenJobsManager}"      
        Margin="2,4,2,2" 
        Height="32" >
        <MenuItem.Icon>
            <Image Source="/MiniSqlAgentConsole;component/Images/btn_032_1101.png"      
                Height="24" 
                Width="24"/>
        </MenuItem.Icon>
    </MenuItem>

</MenuItem>

Prima modifica, creiamo un nuovo menu intitolato Jobs, con al suo interno un’opzione Jobs Manager, per cui abbiamo aggiunto un command nuovo alla classe MiniSqlAgentsCommands, questo nuovo menu sarà inserito fra il menu Service ed il menu Tools

mini_sql_agent_05_02

<TabControl Name="tbcMdi"  Grid.Row="1" Margin="0" >
</TabControl>

Seconda modifica, sostituiamo il contenuto della seconda riga della Grid principale con un TabControl che chiameremo tbcMDI (nessuna paura non è contagioso). Vediamo ora l’event handler che abbiamo associato al command del menu precedentemente creato:

private void OpenJobsManager_Executed(object sender, ExecutedRoutedEventArgs e)
{
    CloseableTabItem tab = new CloseableTabItem();
    ScrollViewer panel = new ScrollViewer();
    panel.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
    tab.Content = panel;
    tab.Header = Properties.Resources.txtMWJobsManagerTab;

    MiniSqlAgentJobsManager jobM = new MiniSqlAgentJobsManager();

    panel.Content = jobM;

    tab.CloseTab += tab_CloseTab;

    tbcMdi.Items.Add(tab);
    tab.IsSelected = true;

}

Il command per aprire il Job Manager genera un CloseableTabItem all’interno del Tab control appena inserito nella nostra MainWindow, e al suo interno crea uno ScrollViewer nel cui contenuto inserisce uno User Control di tipo MiniSqlAgentJobManager, che discuteremo fra poco, e aggancia un event handler al CloseTab del TabItem.

public static readonly RoutedCommand OpenJobsManager = new RoutedCommand();

Nella classe MiniSqlAgentCommands abbiamo aggiunto il RoutedCommand per pilotare l’open job manager.

private void tab_CloseTab(object sender, RoutedEventArgs e)
{
    CloseableTabItem item = sender as CloseableTabItem;
    tbcMdi.Items.Remove(item);
    e.Handled = true;
}

L’event handler è molto semplice, quello che fa è rimuovere il TabItem che è stato chiuso dalla collection e quindi eliminarlo. faccio notare  la riga e.Handled = true; è importante perché blocca il “bubbling” dell’evento CloseTab evitando che possa essere intercettato da un controllo a più alto livello. Non è indispensabile per la MainWindow visto che non ha controlli contenitore che possono essere dotati di altri CloseableTabItems, ma sarà fondamentale nel Jobs Manager, dove altrimenti potremo avere comportamenti piuttosto strani.

Vediamo ora l’interfaccia del JobManager che vogliamo costruire:

mini_sql_agent_05_03

Si tratta di un interfaccia piuttosto semplice, abbiamo un menu con quattro opzioni:

  • Apri un job esistente
  • Crea un nuovo job
  • Duplica un job esistente
  • Cancella un job

Prima di iniziare a vedere l’interfaccia, la terza opzione implica che dobbiamo essere in grado di duplicare un Job, visto che non l’avevamo implementata in precedenza, per predisporre il Job a questa possibilità implementiamo nella classe MiniSqlAgentJob i metodi necessari alla clonazione dell’entity.

public MiniSqlAgentJob Clone()
{
    MiniSqlAgentJob item = new MiniSqlAgentJob();
    CopyTo(item);
    return (item);
}

public void CopyTo(MiniSqlAgentJob job)
{
    job.ConnectionID = this.ConnectionID;
    job.Description = this.Description;
    job.ExecutionInterval = this.ExecutionInterval;
    job.JobFileName = this.JobFileName;
    job.NextExecutionTime = this.NextExecutionTime;
    job.SqlScript = this.SqlScript;
}

Usualmente, quando implementiamo la clonazione, creiamo sempre questi due metodi, che sono utili soprattutto quando è necessario poi creare una Deep Copy di un oggetto che contiene altri oggetti, pertanto il metodo CopyTo, copia il contenuto della classe in un altra dello stesso tipo, il metodo Clone, crea questa nuova classe e la passa al CopyTo.

Modifiche alla classe MiniSqlAgentControl

Nella stesura del codice del manager, ci siamo accorti di alcune necessità relative allo User Control per la gestione del singolo Job, pertanto abbiamo fatto ad esso alcune modifiche, vediamo quali.

Abbiamo eliminato il Textblock per mostrare il nome del file, perché lo metteremo come titolo sui TabItem quindi risulta superfluo, per evitare di occupare molto spazio solo per inserire le due icone di stato del controllo abbiamo spostato l’icona che indica se il Job è valido e quella che ci indica se siamo in modifica sulla fascia in basso che contiene i pulsanti di azione dello user control.

Abbiamo poi aggiunto alcune cose nel code behind del controllo per poterlo pilotare dal manager.

public event RoutedEventHandler Closed
{
    add
    {
        AddHandler(ClosedEvent, value);
    }
    remove
    {
        RemoveHandler(ClosedEvent, value);
    }
}

public event RoutedEventHandler Saved
{
    add
    {
        AddHandler(SavedEvent, value);
    }
    remove
    {
        RemoveHandler(SavedEvent, value);
    }
}

public string JobFileName
{
    get
    {
        return (mJobModel.Job.JobFileName);
    }
}

public object JobControlParent
{
    get;
    set;
}

Oltre ad esporre gli event handler per i due routed event Closed e Saved che ci permettono di agganciare logica nell’interfaccia contenitore collegata a quanto accade nello user control, abbiamo esposto il Nome del file del Job, ed abbiamo creato una property per poter ospitare all’interno dello User Control un riferimento al suo TabItem contenitore, vedremo a cosa ci servirà.

public void Clone(string destinationFile)
{
    MiniSqlAgentJob newJob = mJobModel.Job.Clone();
    newJob.JobFileName = destinationFile;
    newJob.Description = newJob.Description + "(Cloned)";
    newJob.WriteXml(destinationFile);
}

Il metodo per clonare un Job, visto che lo user control è il solo che conosce l’oggetto Job e ne contiene il suo stato attuale, abbiamo assegnato ad esso il compito di creare copia del Job e salvarla con il nuovo nome indicato dall’utente.

public void Init(string fileName)
{
    mJobModel = new MiniSqlAgentJobModel(fileName);
    //After loading a job reset the modified flag
    this.DataContext = mJobModel;
    mJobModel.Modified = false;
}

Dopo il caricamento del Job abbiamo inserito il reset del flag che indica che il contenuto è stato modificato.

public void SetEditMode(bool editMode)
{
    mJobModel.IsInEditMode = editMode;
    if (editMode)
    {
        this.txtDescription.Focus();
        this.txtDescription.SelectionStart = 0;
        this.txtDescription.SelectionLength = this.txtDescription.Text.Length;
    }
}

Infine abbiamo predisposto un metodo per poter attivare la modifica sul controllo programmaticamente, questo ci permetterà di fare in modo che quando l’utente crea un nuovo Job venga automaticamente attivata la sua modifica e venga selezionato il campo descrizione così che l’utente non debba usare il mouse per iniziare a scrivere.

Modifiche a MiniSqlAgentJobModel

In questo caso abbiamo fatto una sola modifica, legata alla gestione del fatto che il Job sia stato modificato, essendovi un comportamento “circolare” nella gestione di questo flag, gestito dal property changed delle property dell’oggetto Job, abbiamo modificato l’event handler nel seguente modo:

void Job_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case MiniSqlAgentJob.FLD_ConnectionID:
        case MiniSqlAgentJob.FLD_Description:
        case MiniSqlAgentJob.FLD_ExecutionInterval:
        case MiniSqlAgentJob.FLD_NextExecutionTime:
        case MiniSqlAgentJob.FLD_SqlScript:
            if (IsInEditMode)
            {
                Modified = true;
                OnPropertyChanged(FLD_StatusInstruction);

            }
            break;
    }
}

Abbiamo agganciato il property changed delle sole property necessarie, inoltre abbiamo legato il flag di modifica al fatto che il controllo sia in modalità edit. Abbiamo verificato che il comportamento anomalo era dovuto al fatto che abbiamo gestito la data e l’ora della prossima esecuzione del Job utilizzando due diversi controlli, questo determina il fatto che quando viene cambiato il valore della data e poi quello dell’ora all’aggancio del Job in binding venga sollevato un PropertyChanged non voluto.

La classe MiniSqlAgentJobManager

<UserControl x:Class="Dnw.MiniSqlAgent.Console.Controls.MiniSqlAgentJobsManager"
    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"   
    xmlns:p="clr-namespace:Dnw.MiniSqlAgent.Console.Properties"
    xmlns:gcmds="clr-namespace:Dnw.Base.Wpf.Commands;assembly=Dnw.Base.Wpf.v4.0"
    xmlns:lcmds="clr-namespace:Dnw.MiniSqlAgent.Console.Commands"
    xmlns:gconv="clr-namespace:Dnw.Base.Wpf.Converters;assembly=Dnw.Base.Wpf.v4.0"
    mc:Ignorable="d"   
    d:DesignHeight="640" 
    d:DesignWidth="900">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary 
                    Source="pack://application:,,,/Dnw.Base.Wpf.v4.0;component/Styles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <gconv:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
        </ResourceDictionary>
    </UserControl.Resources>
    <UserControl.CommandBindings>
        <CommandBinding      
            Command="ApplicationCommands.New"      
            Executed="New_Executed"/>
        <CommandBinding
            Command="{x:Static gcmds:EditingCommands.Clone}"      
            CanExecute="Clone_CanExecute"
            Executed="Clone_Executed"/>
        <CommandBinding
            Command="{x:Static lcmds:MiniSqlAgentCommands.OpenJob}"      
            CanExecute="OpenJob_CanExecute"
            Executed="OpenJob_Executed"/>
        <CommandBinding
            Command="ApplicationCommands.Delete"      
            CanExecute="Delete_CanExecute"
            Executed="Delete_Executed"/>
 
    </UserControl.CommandBindings>    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0"      
                Style="{StaticResource MainInstruction}"      
                Text="{x:Static p:Resources.txtMSAJMMainInstruction}"       
                HorizontalAlignment="Left"      
                VerticalAlignment="Center" 
                Margin="5,2,5,2"/>
        </Grid>
        <Menu 
            Grid.Row="1" 
            Grid.ColumnSpan="2"  
            Margin="0"    
            VerticalAlignment="Top" 
            BorderThickness="2"   
            Foreground="Black" 
            FontSize="16" 
            FontWeight="Normal">
            <MenuItem     
               Header="{x:Static p:Resources.mnuMSAJMOpenJob}"      
               VerticalAlignment="Center"      
               Command="{x:Static lcmds:MiniSqlAgentCommands.OpenJob}"
               Margin="2,4,2,2" 
               Height="32" >
               <MenuItem.Icon>
                   <Image Source="/MiniSqlAgentConsole;component/Images/btn_032_394.png"      
                   Height="24" Width="24"/>
                </MenuItem.Icon>
            </MenuItem>
            <MenuItem
                Header="{x:Static p:Resources.mnuMSAJMNew}"      
                VerticalAlignment="Center"      
                Command="ApplicationCommands.New"
                Margin="2,4,2,2" 
                Height="32" >
                <MenuItem.Icon>
                    <Image Source="/MiniSqlAgentConsole;component/Images/btn_032_109.png"      
                        Height="24" 
                        Width="24"/>
                </MenuItem.Icon>
            </MenuItem>
            <MenuItem      
                Header="{x:Static p:Resources.mnuMSAJMClone}"      
                VerticalAlignment="Center"      
                Command="{x:Static gcmds:EditingCommands.Clone}"
                Margin="2,4,2,2" Height="32" >
                <MenuItem.Icon>
                    <Image Source="/MiniSqlAgentConsole;component/Images/btn_032_120.png"      
                        Height="24" 
                        Width="24"/>
                </MenuItem.Icon>
            </MenuItem>
            <MenuItem      
                Header="{x:Static p:Resources.mnuMSAJMDelete}"      
                VerticalAlignment="Center"      
                Command="ApplicationCommands.Delete"
                Margin="2,4,2,2" 
                Height="32" >
                <MenuItem.Icon>
                    <Image Source="/MiniSqlAgentConsole;component/Images/btn_032_110.png"      
                        Height="24" 
                        Width="24"/>
                </MenuItem.Icon>
            </MenuItem>
 
        </Menu>       
        <TabControl 
            Grid.Row="2" 
            Name="tbcContent"
            Visibility="{Binding IsTabVisible, Converter={StaticResource boolToVisibilityConverter}}">
        
        </TabControl>
    </Grid>                  
</UserControl>

Lo XAML dello user control è molto semplice, infatti ospita il menu, ed un Tab Control Abbiamo collegato la visibilità del TabControl al fatto che esista almeno un TabItem al suo interno utilizzando un converter. le opzioni sulla toolbar inserita sono gestite da quattro command, di cui Clone, essendo un command riutilizzabile ovunque lo abbiamo inserito negli EditCommands della libreria di base. OpenJob lo abbiamo invece creato come Command specifico in questa applicazione, Per New e Delete abbiamo invece utilizzato gli ApplicationCommands di sistema. Vediamo ora il code behind.

public partial class MiniSqlAgentJobsManager : UserControl, INotifyPropertyChanged
{
    public MiniSqlAgentJobsManager()
    {
        InitializeComponent();
        this.DataContext = this;
    }

Implementiamo lo User Control e lo agganciamo come View Model a se stesso, visto che non abbiamo bisogno di altro che della property per gestire la visibilità del TabControl.

private void Clone_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
    if (this.tbcContent != null)
    {
        e.CanExecute = this.tbcContent.Items.Count > 0 && this.tbcContent.SelectedItem != null;
    }
}

private void Clone_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
    string jobFileName = GetJobFileName();
    if (!jobFileName.XDwIsNullOrTrimEmpty() && !File.Exists(jobFileName))
    {
        CloseableTabItem tab = tbcContent.SelectedItem as CloseableTabItem;
        MiniSqlAgentJobControl jobC = tab.Tag as MiniSqlAgentJobControl;
        jobC.Clone(jobFileName);

        GenerateJobControl(jobFileName, true);
    }

}

Gli event handler dei command in alfabetico; partiamo con Clone, attivo se abbiamo un TabControl (Il controllo sul null è necessario perché il binding può essere eseguito prima che l’initialize control abbia creato il TabControl), inoltre controlliamo che ci sia almeno un Job item e che uno dei Job sia selezionato.

Per creare la copia di un Job, per prima cosa chiediamo all’utente di dare un nome al file del nuovo job, poi utilizziamo il SelectedItem del TabControl nella cui property Tag avremo inserito (lo vediamo più avanti) un reference allo User Control che gestisce il Job, in cui abbiamo predisposto la funzione di Clonazione. Al termine creiamo un nuovo TabItem in cui inseriremo il nostro nuovo job.

private void Delete_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
    if (tbcContent != null)
    {
        e.CanExecute = tbcContent.SelectedItem != null;
    }
}

private void Delete_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
    if (MessageBox.Show(Properties.Resources.warMSAJMConfirmDeletion,
        Properties.Resources.warGENConfirm, MessageBoxButton.YesNo,
        MessageBoxImage.Question) == MessageBoxResult.Yes)
    {
        CloseableTabItem tab = tbcContent.SelectedItem as CloseableTabItem;
        MiniSqlAgentJobControl jobC = tab.Tag as MiniSqlAgentJobControl;
        string fileName = jobC.JobFileName;
        tab.Close();
        File.Delete(fileName);
    }
}

Gli event handler per il command di cancellazione, il can execute è subordinato all’esistenza del TabControl ed al fatto che esista un TabItem selezionato. La funzione di cancellazione chiede conferma, chiude il Tab e cancella il file del job.

private void New_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
    string jobFileName = GetJobFileName();
    if (!jobFileName.XDwIsNullOrTrimEmpty() && !File.Exists(jobFileName))
    {
        GenerateJobControl(jobFileName, true);
    }
    else
    {
        MessageBox.Show(Properties.Resources.warMSAJMNewFileNameNotValidOrExistent);
    }
}

L’event handler per il command New, è sempre attivo, quindi niente CanExecute, per creare un nuovo Job semplicemente chiediamo all’utente un nome di file, verifichiamo che il file non esista già su disco, poi creiamo un nuovo TabItem e relativo User Control.

private void OpenJob_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
    e.CanExecute = !ServiceContext.Instance.SettingsManager.DataFolder.XDwIsNullOrTrimEmpty()
        && Directory.Exists(ServiceContext.Instance.SettingsManager.DataFolder)
        && Directory.EnumerateFiles(ServiceContext.Instance.SettingsManager.DataFolder,
        "*", SearchOption.AllDirectories).Any();
}

private void OpenJob_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
    string jobFileName = GetJobFileName();
    if (!jobFileName.XDwIsNullOrTrimEmpty() && File.Exists(jobFileName))
    {
        GenerateJobControl(jobFileName, false);
    }
    else
    {
        MessageBox.Show(Properties.Resources.warMSAJMFileToOpenNotFoundOrNotGiven);
    }

}

Gli event handler per il command Open Job, in questo caso per attivare il bottone facciamo vari test, ovvero controlliamo che l’utente abbia definito la cartella dati ove mettere i Jobs, che la cartella esista e che esista almeno un file nella cartella o nelle sue sottocartelle.

Per aprire il file non facciamo nulla di diverso da quello che facciamo per creare un nuovo file, solo che in questo caso il file deve esistere.

private bool ExistTab(string jobFileName)
{
    bool ret = false;

    foreach (object item in tbcContent.Items)
    {
        CloseableTabItem tabItem = item as CloseableTabItem;
        if (tabItem.TabUniqueID == jobFileName)
        {
            tbcContent.SelectedItem = tabItem;
            ret = true;
            break;
        }
    }
    return (ret);
}

Iniziamo i metodi accessori alla gestione dei Job con il metodo ExistTab, utilizziamo il TabUniqueID che abbiamo aggiunto alla classe CloseableTabItem per verificare che il TabItem di un Job non sia già stato creato per evitare che lo stesso Job possa essere aperto più di una volta creando conflitti in caso di modifiche in più istanze. Se un Job è già aperto lo portiamo in primo piano facendolo divenire il SelectedTab.

private void GenerateJobControl(string jobFileName, bool editMode)
{
    if (!ExistTab(jobFileName))
    {
        CloseableTabItem tab = new CloseableTabItem();
        Grid panel = new Grid();
        panel.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });

        tab.Content = panel;
        tab.Header = jobFileName;
        tab.TabUniqueID = jobFileName;
        MiniSqlAgentJobControl jobC = new MiniSqlAgentJobControl();
        jobC.Closed += JobItem_Closed;
        jobC.Saved += Job_Saved;
        jobC.JobControlParent = tab;
        tab.Tag = jobC;
        panel.Children.Add(jobC);
        Grid.SetColumn(jobC, 0);
        Grid.SetRow(jobC, 0);

        tab.CloseTab += TabItem_CloseTab;

        tbcContent.Items.Add(tab);
        jobC.Init(jobFileName);
        tab.IsSelected = true;
        OnPropertyChanged(FLD_IsTabVisible);

        //This is a trick to be sure that the control is set in edit mode after all
        //the operations on the visual tree of the context are finished
        this.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
        {

            jobC.SetEditMode(editMode);
        }));

    }
}

Per generare il TabItem di un Job ed inserirvi lo UserControl disegnato nei post precedenti utilizziamo quello che abbiamo già visto nel post Generazione di Componenti da Codice in WPF e creiamo a runtime i componenti necessari con la seguente logica:

  • Creiamo un CloseableTabItem.
  • Creiamo una Grid con una sola riga.
  • La inseriamo nel tab item a cui diamo come titolo il nome del file del Job che mettiamo anche nel suo TabUniqueID.
  • Creiamo un MiniSqlAgentJobControl e vi agganciamo gli event handler per gli eventi Closed e Saved.
  • Inseriamo un reference al TabItem nella property JobControlParent dello user control.
  • Inseriamo nel Tag del TabItem un reference allo user control.
  • Aggiungiamo ai figli della Grid il nostro user control.
  • Agganciamo all’evento CloseTab l’event handler per chiudere il tab.
  • Aggiungiamo il TabItem al Tab Control
  • Inizializziamo lo user control con il Job che deve gestire
  • Lo indichiamo come SelectedItem
  • Aggiorniamo la property di visibilità del TabControl
  • Infine andiamo a indicare allo UserControl se deve predisporsi in modifica, e per farlo utilizziamo un trucco particolare, ovvero utilizziamo la gestione asincrona del Dispatcher dello User Control del Manager per assicurarci che l’operazione che attiva la textbox sia eseguita solamente dopo che lo User Control ha terminato tutte le operazioni di “redraw” dell’interfaccia. Se non utilizziamo questo approccio, la textbox non verrà attivata e il suo contenuto selezionato in modo che l’utente possa iniziare a scrivere. Il motivo per cui non accade è semplicemente un problema di tempi, se diamo il focus alla textbox prima che il TabControl abbia portato il TabItem in primo piano il comando non ha effetto.
private string GetJobFileName()
{
    string fileName = null;
    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Title = Properties.Resources.txtMSAJMOpenJobTitle;
    ofd.DefaultExt = EXT_XML;
    ofd.Multiselect = false;
    ofd.Filter = TXT_FILTER;
    ofd.FilterIndex = 1;
    ofd.CheckFileExists = false;
    ofd.CheckPathExists = true;
    bool? ret = ofd.ShowDialog();
    if (ret.HasValue && ret.Value)
    {
        fileName = ofd.FileName;
    }
    return (fileName);
}

Il metodo per richiedere il nome del file di un Job all’utente, sia questo un nuovo file (per i command New e Clone) o un file esistente (Open).

void Job_Closed(object sender, RoutedEventArgs e)
{
    MiniSqlAgentJobControl jobC = sender as MiniSqlAgentJobControl;
    if (jobC != null)
    {
        CloseableTabItem tabItem = jobC.JobControlParent as CloseableTabItem;
        tabItem.Close();
    }
    e.Handled = true;
}

void Job_Saved(object sender, RoutedEventArgs e)
{
    MiniSqlAgentJobControl jobC = sender as MiniSqlAgentJobControl;

    StatusManager.Notify(this, string.Format(Properties.Resources.txtMSAJMJobSaved, jobC.JobFileName));
}

Gli event handler dei pulsanti Save e Close dei Jobs, in caso di pressione del tasto close viene utilizzato il JobControlParent per chiudere il Tab contenitore del Job. Mentre invece il pulsante Saved Notifica che il Job è stato salvato alla UI, lo StatusManager sarà oggetto del prossimo post, che indicherà come far comunicare elementi dell’interfaccia utente non direttamente correlati.

private void TabItem_CloseTab(object sender, RoutedEventArgs e)
{
    CloseableTabItem tab = sender as CloseableTabItem;

    if (tab != null)
    {
        tbcContent.Items.Remove(tab);
    }
    //Important or the routed event will bubble
    //To the control container and if there are CloseableTabItems
    //You can obtaining strange behaviours
    e.Handled = true;
    OnPropertyChanged(FLD_IsTabVisible);
}

Infine l’event handler dell’evento CloseTab dei Tab dei Job, abbiamo evidenziato il fatto che è indispensabile usare e.Handled = true; perché se non viene eseguita questa istruzione l’evento viene propagato allo strato superiore dell’interfaccia e provoca la chiusura del Tab della MainWindow che contiene il Job Manager.

Il codice del progetto di esempio collegato a questo articolo è  disponibile al link seguente:

Il codice del progetto di esempio relativo alle nuove librerie comuni è disponibile al link seguente:

Per qualsiasi domanda, approfondimento, o curiosità, utilizzate pure il link alla form di contatto in cima alla pagina o il forum Microsoft Italia su cui rispondo regolarmente.