Share via


Vytvoření a spuštění dlouhotrvajícího pracovního postupu

Jednou z hlavních funkcí windows Workflow Foundation (WF) je schopnost modulu runtime zachovat a uvolnit nečinné pracovní postupy do databáze. Kroky v části Postupy: Spuštění pracovního postupu demonstrovalo základy hostování pracovních postupů pomocí konzolové aplikace. Příklady spouštění pracovních postupů, obslužných rutin životního cyklu pracovního postupu a obnovení záložek Aby bylo možné efektivně předvést trvalost pracovního postupu, je vyžadován složitější hostitel pracovního postupu, který podporuje spouštění a obnovování více instancí pracovního postupu. Tento krok v tomto kurzu ukazuje, jak vytvořit hostitelskou aplikaci formuláře Windows, která podporuje spouštění a obnovení více instancí pracovního postupu, trvalost pracovních postupů a poskytuje základ pro pokročilé funkce, jako je sledování a správa verzí, které jsou demonstrována v dalších krocích kurzu.

Poznámka:

Tento krok kurzu a následné kroky používají všechny tři typy pracovních postupů: Vytvoření pracovního postupu.

Vytvoření databáze trvalosti

  1. Otevřete APLIKACI SQL Server Management Studio a připojte se k místnímu serveru, například .\SQLEXPRESS. Klikněte pravým tlačítkem myši na uzel Databáze na místním serveru a vyberte Nová databáze. Pojmenujte novou databázi WF45GettingStartedTutorial, přijměte všechny ostatní hodnoty a vyberte OK.

    Poznámka:

    Před vytvořením databáze se ujistěte, že máte na místním serveru oprávnění k vytvoření databáze.

  2. V nabídce Soubor zvolte Otevřít, Soubor. Přejděte do následující složky: C:\Windows\Microsoft.NET\Framework\v4.0.30319\sql\en

    Vyberte následující dva soubory a klikněte na Otevřít.

    • SqlWorkflowInstanceStoreLogic.sql

    • SqlWorkflowInstanceStoreSchema.sql

  3. V nabídce Okno zvolte SqlWorkflowInstanceStoreSchema.sql. Ujistěte se, že je v rozevíracím seznamu Dostupné databáze vybraná možnost WF45GettingStartedTutorial a v nabídce Dotaz zvolte Spustit.

  4. V nabídce Okno zvolte SqlWorkflowInstanceStoreLogic.sql. Ujistěte se, že je v rozevíracím seznamu Dostupné databáze vybraná možnost WF45GettingStartedTutorial a v nabídce Dotaz zvolte Spustit.

    Upozorňující

    Je důležité provést předchozí dva kroky ve správném pořadí. Pokud jsou dotazy prováděny mimo pořadí, dojde k chybám a databáze trvalosti není správně nakonfigurovaná.

Přidání odkazu na sestavení DurableInstancing

  1. Klikněte pravým tlačítkem na NumberGuessWorkflowHost v Průzkumník řešení a vyberte Přidat odkaz.

  2. V seznamu Přidat odkaz vyberte sestavenía zadejte DurableInstancing do pole Hledat sestavení. Tím se vyfiltrují sestavení a usnadní se výběr požadovaných odkazů.

  3. Zaškrtněte políčko vedle položky System.Activities.DurableInstancing a System.Runtime.DurableInstancing ze seznamu výsledků hledání a klepněte na tlačítko OK.

Vytvoření formuláře hostitele pracovního postupu

  1. Klikněte pravým tlačítkem na NumberGuessWorkflowHost v Průzkumník řešení a zvolte Přidat, Nová položka.

  2. V seznamu Nainstalované šablony zvolte Formulář Systému Windows, zadejte WorkflowHostForm do pole Název a klepněte na tlačítko Přidat.

  3. Ve formuláři nakonfigurujte následující vlastnosti.

    Vlastnost Hodnota
    Formborderstyle Fixedsingle
    Maximizebox False
    Velikost 400, 420
  4. Do formuláře v uvedeném pořadí přidejte následující ovládací prvky a nakonfigurujte vlastnosti podle pokynů.

    Ovládací prvek Vlastnost: Hodnota
    Tlačítko Název: NewGame

    Umístění: 13, 13

    Velikost: 75, 23

    Text: Nová hra
    Popisek Umístění: 94, 18

    Text: Odhad čísla od 1 do
    ComboBox Název: NumberRange

    DropDownStyle: DropDownList

    Položky: 10, 100, 1000

    Umístění: 228, 12

    Velikost: 143, 21
    Popisek Umístění: 13, 43

    Text: Typ pracovního postupu
    ComboBox Název: WorkflowType

    DropDownStyle: DropDownList

    Položky: StateMachineNumberGuessWorkflow, FlowchartNumberGuessWorkflow, SequentialNumberGuessWorkflow

    Umístění: 94, 40

    Velikost: 277, 21
    Popisek Název: WorkflowVersion

    Umístění: 13, 362

    Text: Verze pracovního postupu
    GroupBox Umístění: 13, 67

    Velikost: 358, 287

    Text: Hra

    Poznámka:

    Při přidávání následujících ovládacích prvků je vložte do GroupBoxu.

    Ovládací prvek Vlastnost: Hodnota
    Popisek Umístění: 7, 20

    Text: ID instance pracovního postupu
    ComboBox Název: InstanceId

    DropDownStyle: DropDownList

    Umístění: 121, 17

    Velikost: 227, 21
    Popisek Umístění: 7, 47

    Text: Odhad
    TextBox Název: Odhad

    Umístění: 50, 44

    Velikost: 65, 20
    Tlačítko Název: EnterGuess

    Umístění: 121, 42

    Velikost: 75, 23

    Text: Zadejte odhad.
    Tlačítko Název: QuitGame

    Umístění: 274, 42

    Velikost: 75, 23

    Text: Ukončit
    TextBox Název: WorkflowStatus

    Umístění: 10, 73

    Víceřádkový: True

    ReadOnly: True

    Posuvníky: Svisle

    Velikost: 338, 208
  5. Nastavte AcceptButton vlastnost formuláře EnterGuess.

Následující příklad znázorňuje dokončený formulář.

Screenshot of a Windows Workflow Foundation Workflow Host Form.

Přidání vlastností a pomocných metod formuláře

Kroky v této části přidávají vlastnosti a pomocné metody do třídy formuláře, které konfigurují uživatelské rozhraní formuláře tak, aby podporovaly spouštění a obnovení pracovních postupů odhadu čísel.

  1. Pravým tlačítkem myši klikněte na WorkflowHostForm v Průzkumník řešení a zvolte Zobrazit kód.

  2. Do horní části souboru přidejte následující using příkazy (nebo Imports) s jinými using příkazy (nebo Imports).

    Imports System.Activities
    Imports System.Activities.DurableInstancing
    Imports System.Data.SqlClient
    Imports System.IO
    Imports System.Windows.Forms
    
    using System.Activities;
    using System.Activities.DurableInstancing;
    using System.Data.SqlClient;
    using System.IO;
    using System.Windows.Forms;
    
  3. Do třídy WorkflowHostForm přidejte následující deklarace členů.

    Const connectionString = "Server=.\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI"
    Dim store As SqlWorkflowInstanceStore
    Dim workflowStarting As Boolean
    
    const string connectionString = "Server=.\\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=SSPI";
    SqlWorkflowInstanceStore store;
    bool workflowStarting;
    

    Poznámka:

    Pokud se vaše připojovací řetězec liší, aktualizujte connectionString databázi.

  4. WorkflowInstanceId Přidejte do třídy vlastnostWorkflowFormHost.

    Public ReadOnly Property WorkflowInstanceId() As Guid
        Get
            If InstanceId.SelectedIndex = -1 Then
                Return Guid.Empty
            Else
                Return New Guid(InstanceId.SelectedItem.ToString())
            End If
        End Get
    End Property
    
    public Guid WorkflowInstanceId
    {
        get
        {
            return InstanceId.SelectedIndex == -1 ? Guid.Empty : (Guid)InstanceId.SelectedItem;
        }
    }
    

    Pole InstanceId se seznamem zobrazí seznam id trvalých instancí pracovního postupu a WorkflowInstanceId vlastnost vrátí aktuálně vybraný pracovní postup.

  5. Přidejte obslužnou rutinu události formuláře Load . Pokud chcete přidat obslužnou rutinu, přepněte do návrhového zobrazení formuláře, klikněte v horní části okna Vlastnosti na ikonu Události a poklikejte na Načíst.

    Private Sub WorkflowHostForm_Load(sender As Object, e As EventArgs) Handles Me.Load
    
    End Sub
    
    private void WorkflowHostForm_Load(object sender, EventArgs e)
    {
    
    }
    
  6. Přidejte následující kód do WorkflowHostForm_Load.

    ' Initialize the store and configure it so that it can be used for
    ' multiple WorkflowApplication instances.
    store = New SqlWorkflowInstanceStore(connectionString)
    WorkflowApplication.CreateDefaultInstanceOwner(store, Nothing, WorkflowIdentityFilter.Any)
    
    ' Set default ComboBox selections.
    NumberRange.SelectedIndex = 0
    WorkflowType.SelectedIndex = 0
    
    ListPersistedWorkflows()
    
    // Initialize the store and configure it so that it can be used for
    // multiple WorkflowApplication instances.
    store = new SqlWorkflowInstanceStore(connectionString);
    WorkflowApplication.CreateDefaultInstanceOwner(store, null, WorkflowIdentityFilter.Any);
    
    // Set default ComboBox selections.
    NumberRange.SelectedIndex = 0;
    WorkflowType.SelectedIndex = 0;
    
    ListPersistedWorkflows();
    

    Když se formulář načte, nakonfiguruje se SqlWorkflowInstanceStore pole se seznamem rozsahu a typu pracovního postupu na výchozí hodnoty a trvalé instance pracovního postupu se přidají do pole se seznamem InstanceId .

  7. Přidání obslužné rutiny SelectedIndexChanged pro InstanceId. Chcete-li přidat obslužnou rutinu, přepněte do návrhového zobrazení formuláře, vyberte InstanceId pole se seznamem, klikněte na ikonu Události v horní části okna Vlastnosti a poklikejte na SelectedIndexChanged.

    Private Sub InstanceId_SelectedIndexChanged(sender As Object, e As EventArgs) Handles InstanceId.SelectedIndexChanged
    
    End Sub
    
    private void InstanceId_SelectedIndexChanged(object sender, EventArgs e)
    {
    
    }
    
  8. Přidejte následující kód do InstanceId_SelectedIndexChanged. Kdykoli uživatel vybere pracovní postup pomocí pole se seznamem, tato obslužná rutina aktualizuje stavové okno.

    If InstanceId.SelectedIndex = -1 Then
        Return
    End If
    
    ' Clear the status window.
    WorkflowStatus.Clear()
    
    ' Get the workflow version and display it.
    ' If the workflow is just starting then this info will not
    ' be available in the persistence store so do not try and retrieve it.
    If Not workflowStarting Then
        Dim instance As WorkflowApplicationInstance = _
            WorkflowApplication.GetInstance(WorkflowInstanceId, store)
    
        WorkflowVersion.Text = _
            WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity)
    
        ' Unload the instance.
        instance.Abandon()
    End If
    
    if (InstanceId.SelectedIndex == -1)
    {
        return;
    }
    
    // Clear the status window.
    WorkflowStatus.Clear();
    
    // Get the workflow version and display it.
    // If the workflow is just starting then this info will not
    // be available in the persistence store so do not try and retrieve it.
    if (!workflowStarting)
    {
        WorkflowApplicationInstance instance =
            WorkflowApplication.GetInstance(this.WorkflowInstanceId, store);
    
        WorkflowVersion.Text =
            WorkflowVersionMap.GetIdentityDescription(instance.DefinitionIdentity);
    
        // Unload the instance.
        instance.Abandon();
    }
    
  9. Do třídy formuláře přidejte následující ListPersistedWorkflows metodu.

    Private Sub ListPersistedWorkflows()
        Using localCon As New SqlConnection(connectionString)
            Dim localCmd As String = _
                "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]"
    
            Dim cmd As SqlCommand = localCon.CreateCommand()
            cmd.CommandText = localCmd
            localCon.Open()
            Using reader As SqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection)
    
                While (reader.Read())
                    ' Get the InstanceId of the persisted Workflow.
                    Dim id As Guid = Guid.Parse(reader(0).ToString())
                    InstanceId.Items.Add(id)
                End While
            End Using
        End Using
    End Sub
    
    using (var localCon = new SqlConnection(connectionString))
    {
        string localCmd =
            "SELECT [InstanceId] FROM [System.Activities.DurableInstancing].[Instances] ORDER BY [CreationTime]";
    
        SqlCommand cmd = localCon.CreateCommand();
        cmd.CommandText = localCmd;
        localCon.Open();
        using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
        {
            while (reader.Read())
            {
                // Get the InstanceId of the persisted Workflow.
                Guid id = Guid.Parse(reader[0].ToString());
                InstanceId.Items.Add(id);
            }
        }
    }
    

    ListPersistedWorkflows dotazuje úložiště instancí pro trvalé instance pracovního postupu a přidá ID instance do pole se seznamem cboInstanceId .

  10. Přidejte následující UpdateStatus metodu a odpovídající delegáta do třídy formuláře. Tato metoda aktualizuje stavové okno ve formuláři se stavem aktuálně spuštěného pracovního postupu.

    Private Delegate Sub UpdateStatusDelegate(msg As String)
    Public Sub UpdateStatus(msg As String)
        ' We may be on a different thread so we need to
        ' make this call using BeginInvoke.
        If InvokeRequired Then
            BeginInvoke(New UpdateStatusDelegate(AddressOf UpdateStatus), msg)
        Else
            If Not msg.EndsWith(vbCrLf) Then
                msg = msg & vbCrLf
            End If
    
            WorkflowStatus.AppendText(msg)
    
            ' Ensure that the newly added status is visible.
            WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length
            WorkflowStatus.ScrollToCaret()
        End If
    End Sub
    
    private delegate void UpdateStatusDelegate(string msg);
    public void UpdateStatus(string msg)
    {
        // We may be on a different thread so we need to
        // make this call using BeginInvoke.
        if (InvokeRequired)
        {
            BeginInvoke(new UpdateStatusDelegate(UpdateStatus), msg);
        }
        else
        {
            if (!msg.EndsWith("\r\n"))
            {
                msg += "\r\n";
            }
            WorkflowStatus.AppendText(msg);
    
            WorkflowStatus.SelectionStart = WorkflowStatus.Text.Length;
            WorkflowStatus.ScrollToCaret();
        }
    }
    
  11. Přidejte následující GameOver metodu a odpovídající delegáta do třídy formuláře. Po dokončení pracovního postupu tato metoda aktualizuje uživatelské rozhraní formuláře odebráním ID instance dokončeného pracovního postupu ze pole se seznamem InstanceId .

    Private Delegate Sub GameOverDelegate()
    Private Sub GameOver()
        If InvokeRequired Then
            BeginInvoke(New GameOverDelegate(AddressOf GameOver))
        Else
            ' Remove this instance from the InstanceId combo box.
            InstanceId.Items.Remove(InstanceId.SelectedItem)
            InstanceId.SelectedIndex = -1
        End If
    End Sub
    
    private delegate void GameOverDelegate();
    private void GameOver()
    {
        if (InvokeRequired)
        {
            BeginInvoke(new GameOverDelegate(GameOver));
        }
        else
        {
            // Remove this instance from the combo box.
            InstanceId.Items.Remove(InstanceId.SelectedItem);
            InstanceId.SelectedIndex = -1;
        }
    }
    

Konfigurace úložiště instancí, obslužných rutin životního cyklu pracovního postupu a rozšíření

  1. Přidejte metodu ConfigureWorkflowApplication do třídy formuláře.

    Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication)
    
    End Sub
    
    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
    {
    }
    

    Tato metoda nakonfiguruje WorkflowApplication, přidá požadovaná rozšíření a přidá obslužné rutiny pro události životního cyklu pracovního postupu.

  2. Do ConfigureWorkflowApplicationpole SqlWorkflowInstanceStore zadejte hodnotu .WorkflowApplication

    ' Configure the persistence store.
    wfApp.InstanceStore = store
    
    // Configure the persistence store.
    wfApp.InstanceStore = store;
    
  3. Dále vytvořte StringWriter instanci a přidejte ji do Extensions kolekce objektu WorkflowApplication. StringWriter Když se přidá do rozšíření, zachytí se veškerý WriteLine výstup aktivity. Když se pracovní postup změní na nečinný, WriteLine můžete výstup extrahovat z StringWriter formuláře a zobrazit ho.

    ' Add a StringWriter to the extensions. This captures the output
    ' from the WriteLine activities so we can display it in the form.
    Dim sw As New StringWriter()
    wfApp.Extensions.Add(sw)
    
    // Add a StringWriter to the extensions. This captures the output
    // from the WriteLine activities so we can display it in the form.
    var sw = new StringWriter();
    wfApp.Extensions.Add(sw);
    
  4. Přidejte následující obslužnou rutinu Completed události. Po úspěšném dokončení pracovního postupu se v okně stavu zobrazí počet provedených kroků, aby bylo odhadnuto číslo. Pokud pracovní postup skončí, zobrazí se informace o výjimce, které způsobily ukončení. Na konci obslužné rutiny GameOver je volána metoda, která odebere dokončený pracovní postup ze seznamu pracovních postupů.

    wfApp.Completed = _
        Sub(e As WorkflowApplicationCompletedEventArgs)
            If e.CompletionState = ActivityInstanceState.Faulted Then
                UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}")
            ElseIf e.CompletionState = ActivityInstanceState.Canceled Then
                UpdateStatus("Workflow Canceled.")
            Else
                Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns"))
                UpdateStatus($"Congratulations, you guessed the number in {turns} turns.")
            End If
            GameOver()
        End Sub
    
    wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
    {
        if (e.CompletionState == ActivityInstanceState.Faulted)
        {
            UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}");
        }
        else if (e.CompletionState == ActivityInstanceState.Canceled)
        {
            UpdateStatus("Workflow Canceled.");
        }
        else
        {
            int turns = Convert.ToInt32(e.Outputs["Turns"]);
            UpdateStatus($"Congratulations, you guessed the number in {turns} turns.");
        }
        GameOver();
    };
    
  5. Přidejte následující Aborted a OnUnhandledException obslužné rutiny. Metoda GameOver není volána z Aborted obslužné rutiny, protože když je instance pracovního postupu přerušena, neukončí a je možné obnovit instanci později.

    wfApp.Aborted = _
        Sub(e As WorkflowApplicationAbortedEventArgs)
            UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}")
        End Sub
    
    wfApp.OnUnhandledException = _
        Function(e As WorkflowApplicationUnhandledExceptionEventArgs)
            UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}")
            GameOver()
            Return UnhandledExceptionAction.Terminate
        End Function
    
    wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
    {
        UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}");
    };
    
    wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
    {
        UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}");
        GameOver();
        return UnhandledExceptionAction.Terminate;
    };
    
  6. Přidejte následující PersistableIdle obslužnou rutinu. Tato obslužná rutina StringWriter načte přidané rozšíření, extrahuje výstup z WriteLine aktivit a zobrazí ho v okně stavu.

    wfApp.PersistableIdle = _
        Function(e As WorkflowApplicationIdleEventArgs)
            ' Send the current WriteLine outputs to the status window.
            Dim writers = e.GetInstanceExtensions(Of StringWriter)()
            For Each writer In writers
                UpdateStatus(writer.ToString())
            Next
            Return PersistableIdleAction.Unload
        End Function
    
    wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
    {
        // Send the current WriteLine outputs to the status window.
        var writers = e.GetInstanceExtensions<StringWriter>();
        foreach (var writer in writers)
        {
            UpdateStatus(writer.ToString());
        }
        return PersistableIdleAction.Unload;
    };
    

    Výčet PersistableIdleAction má tři hodnoty: None, Persista Unload. Persist způsobí zachování pracovního postupu, ale nezpůsobí uvolnění pracovního postupu. Unload způsobí trvalé a uvolněné pracovní postup.

    Následující příklad je dokončená ConfigureWorkflowApplication metoda.

    Private Sub ConfigureWorkflowApplication(wfApp As WorkflowApplication)
        ' Configure the persistence store.
        wfApp.InstanceStore = store
    
        ' Add a StringWriter to the extensions. This captures the output
        ' from the WriteLine activities so we can display it in the form.
        Dim sw As New StringWriter()
        wfApp.Extensions.Add(sw)
    
        wfApp.Completed = _
            Sub(e As WorkflowApplicationCompletedEventArgs)
                If e.CompletionState = ActivityInstanceState.Faulted Then
                    UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}{vbCrLf}{e.TerminationException.Message}")
                ElseIf e.CompletionState = ActivityInstanceState.Canceled Then
                    UpdateStatus("Workflow Canceled.")
                Else
                    Dim turns As Integer = Convert.ToInt32(e.Outputs("Turns"))
                    UpdateStatus($"Congratulations, you guessed the number in {turns} turns.")
                End If
                GameOver()
            End Sub
    
        wfApp.Aborted = _
            Sub(e As WorkflowApplicationAbortedEventArgs)
                UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}{vbCrLf}{e.Reason.Message}")
            End Sub
    
        wfApp.OnUnhandledException = _
            Function(e As WorkflowApplicationUnhandledExceptionEventArgs)
                UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}{vbCrLf}{e.UnhandledException.Message}")
                GameOver()
                Return UnhandledExceptionAction.Terminate
            End Function
    
        wfApp.PersistableIdle = _
            Function(e As WorkflowApplicationIdleEventArgs)
                ' Send the current WriteLine outputs to the status window.
                Dim writers = e.GetInstanceExtensions(Of StringWriter)()
                For Each writer In writers
                    UpdateStatus(writer.ToString())
                Next
                Return PersistableIdleAction.Unload
            End Function
    End Sub
    
    private void ConfigureWorkflowApplication(WorkflowApplication wfApp)
    {
        // Configure the persistence store.
        wfApp.InstanceStore = store;
    
        // Add a StringWriter to the extensions. This captures the output
        // from the WriteLine activities so we can display it in the form.
        var sw = new StringWriter();
        wfApp.Extensions.Add(sw);
    
        wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
        {
            if (e.CompletionState == ActivityInstanceState.Faulted)
            {
                UpdateStatus($"Workflow Terminated. Exception: {e.TerminationException.GetType().FullName}\r\n{e.TerminationException.Message}");
            }
            else if (e.CompletionState == ActivityInstanceState.Canceled)
            {
                UpdateStatus("Workflow Canceled.");
            }
            else
            {
                int turns = Convert.ToInt32(e.Outputs["Turns"]);
                UpdateStatus($"Congratulations, you guessed the number in {turns} turns.");
            }
            GameOver();
        };
    
        wfApp.Aborted = delegate(WorkflowApplicationAbortedEventArgs e)
        {
            UpdateStatus($"Workflow Aborted. Exception: {e.Reason.GetType().FullName}\r\n{e.Reason.Message}");
        };
    
        wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
        {
            UpdateStatus($"Unhandled Exception: {e.UnhandledException.GetType().FullName}\r\n{e.UnhandledException.Message}");
            GameOver();
            return UnhandledExceptionAction.Terminate;
        };
    
        wfApp.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
        {
            // Send the current WriteLine outputs to the status window.
            var writers = e.GetInstanceExtensions<StringWriter>();
            foreach (var writer in writers)
            {
                UpdateStatus(writer.ToString());
            }
            return PersistableIdleAction.Unload;
        };
    }
    

Povolení spuštění a obnovení více typů pracovních postupů

Aby bylo možné obnovit instanci pracovního postupu, musí hostitel zadat definici pracovního postupu. V tomto kurzu existují tři typy pracovních postupů a následné kroky kurzu představují více verzí těchto typů. WorkflowIdentity poskytuje způsob, jak hostitelská aplikace přidružit identifikaci informací k trvalé instanci pracovního postupu. Kroky v této části ukazují, jak vytvořit třídu nástrojů, která vám pomůže s mapováním identity pracovního postupu z trvalé instance pracovního postupu na odpovídající definici pracovního postupu. Další informace o WorkflowIdentity správě verzí a správu verzí naleznete v tématu Použití WorkflowIdentity a správy verzí.

  1. Klikněte pravým tlačítkem na NumberGuessWorkflowHost v Průzkumník řešení a zvolte Přidat, Třída. Zadejte WorkflowVersionMap do pole Název a klikněte na Přidat.

  2. Do horní části souboru přidejte následující using příkazy nebo Imports příkazy s jinými using příkazy nebo Imports příkazy.

    Imports System.Activities
    Imports NumberGuessWorkflowActivities
    
    using System.Activities;
    using NumberGuessWorkflowActivities;
    
  3. WorkflowVersionMap Nahraďte deklaraci třídy následující deklarací.

    Public Module WorkflowVersionMap
        Dim map As Dictionary(Of WorkflowIdentity, Activity)
    
        ' Current version identities.
        Public StateMachineNumberGuessIdentity As WorkflowIdentity
        Public FlowchartNumberGuessIdentity As WorkflowIdentity
        Public SequentialNumberGuessIdentity As WorkflowIdentity
    
        Sub New()
            map = New Dictionary(Of WorkflowIdentity, Activity)
    
            ' Add the current workflow version identities.
            StateMachineNumberGuessIdentity = New WorkflowIdentity With
            {
                .Name = "StateMachineNumberGuessWorkflow",
                .Version = New Version(1, 0, 0, 0)
            }
    
            FlowchartNumberGuessIdentity = New WorkflowIdentity With
            {
                .Name = "FlowchartNumberGuessWorkflow",
                .Version = New Version(1, 0, 0, 0)
            }
    
            SequentialNumberGuessIdentity = New WorkflowIdentity With
            {
                .Name = "SequentialNumberGuessWorkflow",
                .Version = New Version(1, 0, 0, 0)
            }
    
            map.Add(StateMachineNumberGuessIdentity, New StateMachineNumberGuessWorkflow())
            map.Add(FlowchartNumberGuessIdentity, New FlowchartNumberGuessWorkflow())
            map.Add(SequentialNumberGuessIdentity, New SequentialNumberGuessWorkflow())
        End Sub
    
        Public Function GetWorkflowDefinition(identity As WorkflowIdentity) As Activity
            Return map(identity)
        End Function
    
        Public Function GetIdentityDescription(identity As WorkflowIdentity) As String
            Return identity.ToString()
        End Function
    End Module
    
    public static class WorkflowVersionMap
    {
        static Dictionary<WorkflowIdentity, Activity> map;
    
        // Current version identities.
        static public WorkflowIdentity StateMachineNumberGuessIdentity;
        static public WorkflowIdentity FlowchartNumberGuessIdentity;
        static public WorkflowIdentity SequentialNumberGuessIdentity;
    
        static WorkflowVersionMap()
        {
            map = new Dictionary<WorkflowIdentity, Activity>();
    
            // Add the current workflow version identities.
            StateMachineNumberGuessIdentity = new WorkflowIdentity
            {
                Name = "StateMachineNumberGuessWorkflow",
                Version = new Version(1, 0, 0, 0)
            };
    
            FlowchartNumberGuessIdentity = new WorkflowIdentity
            {
                Name = "FlowchartNumberGuessWorkflow",
                Version = new Version(1, 0, 0, 0)
            };
    
            SequentialNumberGuessIdentity = new WorkflowIdentity
            {
                Name = "SequentialNumberGuessWorkflow",
                Version = new Version(1, 0, 0, 0)
            };
    
            map.Add(StateMachineNumberGuessIdentity, new StateMachineNumberGuessWorkflow());
            map.Add(FlowchartNumberGuessIdentity, new FlowchartNumberGuessWorkflow());
            map.Add(SequentialNumberGuessIdentity, new SequentialNumberGuessWorkflow());
        }
    
        public static Activity GetWorkflowDefinition(WorkflowIdentity identity)
        {
            return map[identity];
        }
    
        public static string GetIdentityDescription(WorkflowIdentity identity)
        {
            return identity.ToString();
       }
    }
    

    WorkflowVersionMap obsahuje tři identity pracovního postupu, které se mapují na tři definice pracovního postupu z tohoto kurzu, a používá se v následujících částech při spuštění a obnovení pracovních postupů.

Zahájení nového pracovního postupu

  1. Přidání obslužné rutiny Click pro NewGame. Chcete-li přidat obslužnou rutinu, přepněte do návrhového zobrazení formuláře a poklikejte na NewGametlačítko . Přidá se obslužná NewGame_Click rutina a zobrazení se přepne do zobrazení kódu formuláře. Pokaždé, když uživatel klikne na toto tlačítko, spustí se nový pracovní postup.

    Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click
    
    End Sub
    
    private void NewGame_Click(object sender, EventArgs e)
    {
    
    }
    
  2. Do obslužné rutiny kliknutí přidejte následující kód. Tento kód vytvoří slovník vstupníchargumentch Tento slovník obsahuje jednu položku, která obsahuje oblast náhodně vygenerovaného čísla načteného z pole se seznamem rozsahu.

    Dim inputs As New Dictionary(Of String, Object)()
    inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))
    
    var inputs = new Dictionary<string, object>();
    inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));
    
  3. Dále přidejte následující kód, který spouští pracovní postup. Definice WorkflowIdentity pracovního postupu odpovídající vybranému typu pracovního postupu se načte pomocí WorkflowVersionMap pomocné třídy. Dále se vytvoří nová WorkflowApplication instance pomocí definice WorkflowIdentitypracovního postupu a slovníku vstupních argumentů.

    Dim identity As WorkflowIdentity = Nothing
    Select Case WorkflowType.SelectedItem.ToString()
        Case "SequentialNumberGuessWorkflow"
            identity = WorkflowVersionMap.SequentialNumberGuessIdentity
    
        Case "StateMachineNumberGuessWorkflow"
            identity = WorkflowVersionMap.StateMachineNumberGuessIdentity
    
        Case "FlowchartNumberGuessWorkflow"
            identity = WorkflowVersionMap.FlowchartNumberGuessIdentity
    End Select
    
    Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity)
    
    Dim wfApp = New WorkflowApplication(wf, inputs, identity)
    
    WorkflowIdentity identity = null;
    switch (WorkflowType.SelectedItem.ToString())
    {
        case "SequentialNumberGuessWorkflow":
            identity = WorkflowVersionMap.SequentialNumberGuessIdentity;
            break;
    
        case "StateMachineNumberGuessWorkflow":
            identity = WorkflowVersionMap.StateMachineNumberGuessIdentity;
            break;
    
        case "FlowchartNumberGuessWorkflow":
            identity = WorkflowVersionMap.FlowchartNumberGuessIdentity;
            break;
    };
    
    Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity);
    
    WorkflowApplication wfApp = new WorkflowApplication(wf, inputs, identity);
    
  4. Dále přidejte následující kód, který přidá pracovní postup do seznamu pracovních postupů a zobrazí informace o verzi pracovního postupu ve formuláři.

    ' Add the workflow to the list and display the version information.
    workflowStarting = True
    InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id)
    WorkflowVersion.Text = identity.ToString()
    workflowStarting = False
    
    // Add the workflow to the list and display the version information.
    workflowStarting = true;
    InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id);
    WorkflowVersion.Text = identity.ToString();
    workflowStarting = false;
    
  5. Volání ConfigureWorkflowApplication konfigurace úložiště instancí, rozšíření a obslužných rutin životního cyklu pracovního postupu pro tuto WorkflowApplication instanci

    ' Configure the instance store, extensions, and
    ' workflow lifecycle handlers.
    ConfigureWorkflowApplication(wfApp)
    
    // Configure the instance store, extensions, and
    // workflow lifecycle handlers.
    ConfigureWorkflowApplication(wfApp);
    
  6. Nakonec zavolejte Run.

    ' Start the workflow.
    wfApp.Run()
    
    // Start the workflow.
    wfApp.Run();
    

    Následující příklad je dokončená obslužná NewGame_Click rutina.

    Private Sub NewGame_Click(sender As Object, e As EventArgs) Handles NewGame.Click
        ' Start a new workflow.
        Dim inputs As New Dictionary(Of String, Object)()
        inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem))
    
        Dim identity As WorkflowIdentity = Nothing
        Select Case WorkflowType.SelectedItem.ToString()
            Case "SequentialNumberGuessWorkflow"
                identity = WorkflowVersionMap.SequentialNumberGuessIdentity
    
            Case "StateMachineNumberGuessWorkflow"
                identity = WorkflowVersionMap.StateMachineNumberGuessIdentity
    
            Case "FlowchartNumberGuessWorkflow"
                identity = WorkflowVersionMap.FlowchartNumberGuessIdentity
        End Select
    
        Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(identity)
    
        Dim wfApp = New WorkflowApplication(wf, inputs, identity)
    
        ' Add the workflow to the list and display the version information.
        workflowStarting = True
        InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id)
        WorkflowVersion.Text = identity.ToString()
        workflowStarting = False
    
        ' Configure the instance store, extensions, and
        ' workflow lifecycle handlers.
        ConfigureWorkflowApplication(wfApp)
    
        ' Start the workflow.
        wfApp.Run()
    End Sub
    
    private void NewGame_Click(object sender, EventArgs e)
    {
        var inputs = new Dictionary<string, object>();
        inputs.Add("MaxNumber", Convert.ToInt32(NumberRange.SelectedItem));
    
        WorkflowIdentity identity = null;
        switch (WorkflowType.SelectedItem.ToString())
        {
            case "SequentialNumberGuessWorkflow":
                identity = WorkflowVersionMap.SequentialNumberGuessIdentity;
                break;
    
            case "StateMachineNumberGuessWorkflow":
                identity = WorkflowVersionMap.StateMachineNumberGuessIdentity;
                break;
    
            case "FlowchartNumberGuessWorkflow":
                identity = WorkflowVersionMap.FlowchartNumberGuessIdentity;
                break;
        };
    
        Activity wf = WorkflowVersionMap.GetWorkflowDefinition(identity);
    
        var wfApp = new WorkflowApplication(wf, inputs, identity);
    
        // Add the workflow to the list and display the version information.
        workflowStarting = true;
        InstanceId.SelectedIndex = InstanceId.Items.Add(wfApp.Id);
        WorkflowVersion.Text = identity.ToString();
        workflowStarting = false;
    
        // Configure the instance store, extensions, and
        // workflow lifecycle handlers.
        ConfigureWorkflowApplication(wfApp);
    
        // Start the workflow.
        wfApp.Run();
    }
    

Obnovení pracovního postupu

  1. Přidání obslužné rutiny Click pro EnterGuess. Chcete-li přidat obslužnou rutinu, přepněte do návrhového zobrazení formuláře a poklikejte na EnterGuesstlačítko . Kdykoli uživatel klikne na toto tlačítko, pracovní postup se obnoví.

    Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click
    
    End Sub
    
    private void EnterGuess_Click(object sender, EventArgs e)
    {
    
    }
    
  2. Přidejte následující kód, který zajistí, že je v seznamu pracovních postupů vybraný pracovní postup a zda je odhad uživatele platný.

    If WorkflowInstanceId = Guid.Empty Then
        MessageBox.Show("Please select a workflow.")
        Return
    End If
    
    Dim userGuess As Integer
    If Not Int32.TryParse(Guess.Text, userGuess) Then
        MessageBox.Show("Please enter an integer.")
        Guess.SelectAll()
        Guess.Focus()
        Return
    End If
    
    if (WorkflowInstanceId == Guid.Empty)
    {
        MessageBox.Show("Please select a workflow.");
        return;
    }
    
    int guess;
    if (!Int32.TryParse(Guess.Text, out guess))
    {
        MessageBox.Show("Please enter an integer.");
        Guess.SelectAll();
        Guess.Focus();
        return;
    }
    
  3. Dále načtěte WorkflowApplicationInstance trvalou instanci pracovního postupu. A WorkflowApplicationInstance představuje trvalou instanci pracovního postupu, která ještě nebyla přidružena k definici pracovního postupu. The DefinitionIdentity of the WorkflowApplicationInstance contains the WorkflowIdentity persisted workflow instance. V tomto kurzu WorkflowVersionMap se třída nástroje používá k mapování WorkflowIdentity na správnou definici pracovního postupu. Jakmile se definice pracovního postupu načte, vytvoří se WorkflowApplication pomocí správné definice pracovního postupu.

    Dim instance As WorkflowApplicationInstance = _
        WorkflowApplication.GetInstance(WorkflowInstanceId, store)
    
    ' Use the persisted WorkflowIdentity to retrieve the correct workflow
    ' definition from the dictionary.
    Dim wf As Activity = _
        WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)
    
    ' Associate the WorkflowApplication with the correct definition
    Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity)
    
    WorkflowApplicationInstance instance =
        WorkflowApplication.GetInstance(WorkflowInstanceId, store);
    
    // Use the persisted WorkflowIdentity to retrieve the correct workflow
    // definition from the dictionary.
    Activity wf =
        WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);
    
    // Associate the WorkflowApplication with the correct definition
    var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
    
  4. WorkflowApplication Po vytvoření nakonfigurujte úložiště instancí, obslužné rutiny životního cyklu pracovního postupu a rozšíření voláním ConfigureWorkflowApplication. Tyto kroky musí být provedeny při každém vytvoření nového WorkflowApplication a musí být provedeny před načtením instance pracovního postupu do WorkflowApplication. Po načtení pracovního postupu se obnoví odhad uživatele.

    ' Configure the extensions and lifecycle handlers.
    ' Do this before the instance is loaded. Once the instance is
    ' loaded it is too late to add extensions.
    ConfigureWorkflowApplication(wfApp)
    
    ' Load the workflow.
    wfApp.Load(instance)
    
    ' Resume the workflow.
    wfApp.ResumeBookmark("EnterGuess", userGuess)
    
    // Configure the extensions and lifecycle handlers.
    // Do this before the instance is loaded. Once the instance is
    // loaded it is too late to add extensions.
    ConfigureWorkflowApplication(wfApp);
    
    // Load the workflow.
    wfApp.Load(instance);
    
    // Resume the workflow.
    wfApp.ResumeBookmark("EnterGuess", guess);
    
  5. Nakonec vymažte textové pole pro odhad a připravte formulář na přijetí dalšího odhadu.

    ' Clear the Guess textbox.
    Guess.Clear()
    Guess.Focus()
    
    // Clear the Guess textbox.
    Guess.Clear();
    Guess.Focus();
    

    Následující příklad je dokončená obslužná EnterGuess_Click rutina.

    Private Sub EnterGuess_Click(sender As Object, e As EventArgs) Handles EnterGuess.Click
        If WorkflowInstanceId = Guid.Empty Then
            MessageBox.Show("Please select a workflow.")
            Return
        End If
    
        Dim userGuess As Integer
        If Not Int32.TryParse(Guess.Text, userGuess) Then
            MessageBox.Show("Please enter an integer.")
            Guess.SelectAll()
            Guess.Focus()
            Return
        End If
    
        Dim instance As WorkflowApplicationInstance = _
            WorkflowApplication.GetInstance(WorkflowInstanceId, store)
    
        ' Use the persisted WorkflowIdentity to retrieve the correct workflow
        ' definition from the dictionary.
        Dim wf As Activity = _
            WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)
    
        ' Associate the WorkflowApplication with the correct definition
        Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity)
    
        ' Configure the extensions and lifecycle handlers.
        ' Do this before the instance is loaded. Once the instance is
        ' loaded it is too late to add extensions.
        ConfigureWorkflowApplication(wfApp)
    
        ' Load the workflow.
        wfApp.Load(instance)
    
        ' Resume the workflow.
        wfApp.ResumeBookmark("EnterGuess", userGuess)
    
        ' Clear the Guess textbox.
        Guess.Clear()
        Guess.Focus()
    End Sub
    
    private void EnterGuess_Click(object sender, EventArgs e)
    {
        if (WorkflowInstanceId == Guid.Empty)
        {
            MessageBox.Show("Please select a workflow.");
            return;
        }
    
        int guess;
        if (!Int32.TryParse(Guess.Text, out guess))
        {
            MessageBox.Show("Please enter an integer.");
            Guess.SelectAll();
            Guess.Focus();
            return;
        }
    
        WorkflowApplicationInstance instance =
            WorkflowApplication.GetInstance(WorkflowInstanceId, store);
    
        // Use the persisted WorkflowIdentity to retrieve the correct workflow
        // definition from the dictionary.
        Activity wf =
            WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);
    
        // Associate the WorkflowApplication with the correct definition
        var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
    
        // Configure the extensions and lifecycle handlers.
        // Do this before the instance is loaded. Once the instance is
        // loaded it is too late to add extensions.
        ConfigureWorkflowApplication(wfApp);
    
        // Load the workflow.
        wfApp.Load(instance);
    
        // Resume the workflow.
        wfApp.ResumeBookmark("EnterGuess", guess);
    
        // Clear the Guess textbox.
        Guess.Clear();
        Guess.Focus();
    }
    

Ukončení pracovního postupu

  1. Přidání obslužné rutiny Click pro QuitGame. Chcete-li přidat obslužnou rutinu, přepněte do návrhového zobrazení formuláře a poklikejte na QuitGametlačítko . Kdykoli uživatel klikne na toto tlačítko, ukončí se aktuálně vybraný pracovní postup.

    Private Sub QuitGame_Click(sender As Object, e As EventArgs) Handles QuitGame.Click
    
    End Sub
    
    private void QuitGame_Click(object sender, EventArgs e)
    {
    
    }
    
  2. Do obslužné rutiny QuitGame_Click přidejte následující kód. Tento kód nejprve zkontroluje, jestli je v seznamu pracovních postupů vybraný pracovní postup. Pak načte trvalou instanci do , WorkflowApplicationInstancepoužije DefinitionIdentity k určení správné definice pracovního postupu a pak inicializuje WorkflowApplication. Dále jsou obslužné rutiny životního cyklu rozšíření a pracovního postupu nakonfigurovány s voláním ConfigureWorkflowApplication. Po nakonfigurování WorkflowApplication se načte a pak Terminate se zavolá.

    If WorkflowInstanceId = Guid.Empty Then
        MessageBox.Show("Please select a workflow.")
        Return
    End If
    
    Dim instance As WorkflowApplicationInstance = _
        WorkflowApplication.GetInstance(WorkflowInstanceId, store)
    
    ' Use the persisted WorkflowIdentity to retrieve the correct workflow
    ' definition from the dictionary.
    Dim wf As Activity = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity)
    
    ' Associate the WorkflowApplication with the correct definition.
    Dim wfApp As New WorkflowApplication(wf, instance.DefinitionIdentity)
    
    ' Configure the extensions and lifecycle handlers.
    ConfigureWorkflowApplication(wfApp)
    
    ' Load the workflow.
    wfApp.Load(instance)
    
    ' Terminate the workflow.
    wfApp.Terminate("User resigns.")
    
    if (WorkflowInstanceId == Guid.Empty)
    {
        MessageBox.Show("Please select a workflow.");
        return;
    }
    
    WorkflowApplicationInstance instance =
        WorkflowApplication.GetInstance(WorkflowInstanceId, store);
    
    // Use the persisted WorkflowIdentity to retrieve the correct workflow
    // definition from the dictionary.
    Activity wf = WorkflowVersionMap.GetWorkflowDefinition(instance.DefinitionIdentity);
    
    // Associate the WorkflowApplication with the correct definition
    var wfApp = new WorkflowApplication(wf, instance.DefinitionIdentity);
    
    // Configure the extensions and lifecycle handlers
    ConfigureWorkflowApplication(wfApp);
    
    // Load the workflow.
    wfApp.Load(instance);
    
    // Terminate the workflow.
    wfApp.Terminate("User resigns.");
    

Sestavení a spuštění aplikace

  1. Poklikáním na Program.cs (nebo Module1.vb) v Průzkumník řešení zobrazíte kód.

  2. Do horní části souboru přidejte následující using příkaz (nebo Imports) s jinými using příkazy (nebo Imports).

    Imports System.Windows.Forms
    
    using System.Windows.Forms;
    
  3. Odeberte nebo zakomentujte existující kód hostování pracovního postupu z postupu: Spusťte pracovní postup a nahraďte ho následujícím kódem.

    Sub Main()
        Application.EnableVisualStyles()
        Application.Run(New WorkflowHostForm())
    End Sub
    
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.Run(new WorkflowHostForm());
    }
    
  4. Klikněte pravým tlačítkem na NumberGuessWorkflowHost v Průzkumník řešení a zvolte Vlastnosti. Na kartě Aplikace zadejte aplikaci systému Windows pro typ výstupu. Tento krok je nepovinný, ale pokud není následovaný oknem konzoly, zobrazí se vedle formuláře.

  5. Stisknutím kombinace kláves Ctrl+Shift+B sestavte aplikaci.

  6. Ujistěte se, že je parametr NumberGuessWorkflowHost nastavený jako spouštěcí aplikace, a stisknutím kombinace kláves Ctrl+F5 spusťte aplikaci.

  7. Vyberte oblast pro hádanou hru a typ pracovního postupu, který chcete spustit, a klikněte na Tlačítko Nová hra. Do pole Hádejte zadejte odhad a kliknutím na Přejít odešlete svůj odhad. Všimněte si, že výstup z WriteLine aktivit se zobrazí ve formuláři.

  8. Spusťte několik pracovních postupů pomocí různých typů pracovních postupů a rozsahů čísel, zadejte určité odhady a přepněte mezi pracovními postupy výběrem ze seznamu ID instance pracovního postupu.

    Všimněte si, že když přepnete na nový pracovní postup, předchozí odhady a průběh pracovního postupu se v okně stavu nezobrazí. Důvodem, proč stav není dostupný, je to, že se nezachytí a neuloží kamkoliv. V dalším kroku kurzu Postupy : Vytvoření vlastního účastníka sledování vytvoříte vlastního účastníka sledování, který uloží tyto informace.