Skip to main content
All docs
V25.1
  • AI Chat Control

    • 13 minutes to read

    Note

    The DevExpress AI Chat Control (AIChatControl) can only be used in Windows Forms applications that target the .NET 8+ framework.

    The AI Chat Control (AIChatControl) allows you to incorporate an interactive, Copilot-inspired chat-based UI within your WinForms application. This control leverages BlazorWebView to reuse the DevExpress Blazor AI Chat component (DxAIChat). To use the WinForms AI Chat Control, you must have one of the following active subscriptions:

    View Example: AI Chat

    WinForms AI Chat Control, DevExpress

    Tip

    The following example extends the DevExpress WinForms Chat Client App demo. It creates a Copilot-inspired, AI-powered chat interface without using BlazorWebView. The example uses ‘native’ DevExpress UI controls (such as the GridControl, MemoEdit, and HtmlContentControl). The app targets .NET Framework 4.6.2 or later and integrates with the Azure OpenAI service to support conversational engagement, Markdown rendering, and structured message display within a fully native WinForms environment.

    View Example: WinForms Chat for .NET Framework

    Getting Started

    Install DevExpress NuGet Packages

    1. DevExpress.AIIntegration.WinForms.Chat
    2. DevExpress.Win.Design (enables design-time features for DevExpress UI controls)

    Change Project SDK

    Update the project SDK to Microsoft.NET.Sdk.Razor:

    <Project Sdk="Microsoft.NET.Sdk.Razor">
    

    Register AI Client

    See the following help topic for information on required NuGet packages and system requirements: Register an AI Client.

    The following code snippet registers the Azure OpenAI client:

    using Microsoft.Extensions.AI;
    using DevExpress.AIIntegration;
    
    internal static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            IChatClient azureChatClient = new Azure.AI.OpenAI.AzureOpenAIClient(new Uri(AzureOpenAIEndpoint),
                new System.ClientModel.ApiKeyCredential(AzureOpenAIKey))
                .GetChatClient(ModelId).AsIChatClient();
            AIExtensionsContainerDesktop.Default.RegisterChatClient(azureChatClient);
            Application.Run(new Form1());
        }
        static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } }
        static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } }
        static string ModelId { get { return "gpt-4o-mini"; } }
    }
    

    Create AI Chat Control

    Drop the AIChatControl from the toolbox onto a Form.

    Note

    The AI Chat Control does not support design-time rendering.

    The following code snippet creates the AIChatControl with default settings:

    using DevExpress.AIIntegration.WinForms.Chat;
    
    public partial class Chat : XtraForm {
        public Chat() {
            InitializeComponent();
    
            AIChatControl chat = new AIChatControl(){
                Name = "aiChatControl1",
                Dock = DockStyle.Fill
            };
            this.Controls.Add(chat);
        }
    }
    

    Streaming

    The AI Chat Control can display responses from the AI assistant as they are generated in a natural, conversational flow (rather than waiting for the entire message to complete before showing it to the user). Enable the UseStreaming setting to activate streaming:

    aiChatControl1.UseStreaming = DevExpress.Utils.DefaultBoolean.True;
    

    Play the following animation to see the result:

    Streaming - WinForms AI Chat Control, DevExpress

    Markdown Message Rendering

    • Set the ContentFormat property to Markdown to receive responses as Markdown.
    • Handle the MarkdownConvert event to convert Markdown text into HTML and make responses more readable, structured, and visually appealing.

    The following example enables Markdown message rendering. The example uses the Markdig Markdown processing library to convert Markdown text into HTML.

    public Chat() {
        InitializeComponent();
        aiChatControl1.ContentFormat = DevExpress.AIIntegration.Blazor.Chat.ResponseContentFormat.Markdown
        aiChatControl1.MarkdownConvert += AiChatControl1_MarkdownConvert
    }
    
    void AiChatControl1_MarkdownConvert(object sender, DevExpress.AIIntegration.Blazor.Chat.WebView.AIChatControlMarkdownConvertEventArgs e) {
        e.HtmlText = (MarkupString)Markdown.ToHtml(e.MarkdownText);
    }
    

    The following screenshot shows the result:

    Markdown Message Rendering - WinForms AI Chat Control, DevExpress

    File Attachments

    Users can now attach files directly to their chat messages. The AI analyzes document content (such as text files, PDFs, images) and delivers more context-aware responses.

    File Upload - WinForms AI Chat Control, DevExpress

    To activate file upload:

    1. Enable the FileUploadEnabled property to allow users to attach files.

      aiChatControl1.FileUploadEnabled = DevExpress.Utils.DefaultBoolean.True;
      
    2. Configure additional settings based on your project requirements (the maximum file size, allowed file types/extensions, the maximum number of files that users can attach to a message):

      chat.OptionsFileUpload.FileTypeFilter.AddRange(new List<string> { "text/plain", "application/pdf", "image/png" }); // Allowed MIME types.
      chat.OptionsFileUpload.AllowedFileExtensions.AddRange(new List<string> { ".txt", ".pdf", ".png" });
      chat.OptionsFileUpload.MaxFileCount = 5;
      chat.OptionsFileUpload.MaxFileSize = 5 * 1024 * 1024; // 5 MB
      

    Tip

    See the following article for more information on MIME types (FileTypeFilter): Common Media Types.

    Prompt Suggestions

    To help users get started or explore new possibilities, the DevExpress AI Chat Control can display prompt suggestions.

    Prompt Suggestions - WinForms AI Chat Control

    Use the SetPromptSuggestions method to supply relevant prompts:

    using DevExpress.AIIntegration.Blazor.Chat.WebView;
    
    aiChatControl1.SetPromptSuggestions(new List<PromptSuggestion>(){
        new PromptSuggestion(
            title: "Birthday Wish",
            text: "A warm and cheerful birthday greeting message.",
            prompt: "Write a heartfelt birthday message for a close friend."),
    
        new PromptSuggestion(
            "Thank You Note",
            "A polite thank you note to express gratitude.",
            "Compose a short thank you note to a colleague who helped with a project.")
    });
    

    Customize Empty Text

    Use the EmptyStateText property to specify the message displayed when a chat has yet to start:

    aiChatControl1.EmptyStateText = "AI Assistant is ready to answer your questions.";
    

    The following screenshot shows the result:

    Custom Empty Text - WinForms AI Chat Control, DevExpress

    Customize Chat UI and Appearance

    The AIChatControl supports appearance customization through Razor-based templates. You can customize chat messages, prompt suggestions, and the text displayed when the chat has no message history.

    Message Templates

    Use the following methods to customize chat message container (including paddings and inner content alignment) and content:

    • SetMessageTemplate(RenderFragment<BlazorChatMessage> template)
    • SetMessageContentTemplate(RenderFragment<BlazorChatMessage> template)

    The following example displays a custom button within chat messages. When a user clicks the button, a message box appears.

    Customize Chat Messages - WinForms AI Chat Control, DevExpress

    using DevExpress.XtraEditors;
    using Microsoft.AspNetCore.Components;
    using using DevExpress.AIIntegration.Blazor.Chat;
    
    public Form1() {
        InitializeComponent();
    
        aiChatControl1.SetMessageTemplate(message => builder => {
            builder.OpenComponent<Message>(0);
            builder.AddAttribute(1, "message", message);
            builder.AddAttribute(2, "OnButtonClick", EventCallback.Factory.Create<BlazorChatMessage>(this, CustomButtonClick));
            builder.CloseComponent();
        })
    }
    
    void CustomButtonClick(BlazorChatMessage message) {
        XtraMessageBox.Show($"Message: {message.Content}");
    }
    

    The Message.razor file:

    @using Microsoft.AspNetCore.Components.Web
    @using DevExpress.AIIntegration.Blazor.Chat
    
    <style>
        .my-custom-button {
            background-color: #007acc;
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 4px;
            cursor: pointer;
        }
    
            .my-custom-button:hover {
                background-color: #005fa3;
            }
    </style>
    
    <div class="@GetMessageClasses(message)">
        @if (message.Typing) {
            <span>Loading...</span>
        }
        else {
            <div class="demo-chat-content">
                @message.Content
                <button class="my-custom-button" @onclick="OnButtonClicked">Click me</button>
            </div>
        }
    </div>
    
    @code {
        [Parameter]
        public BlazorChatMessage message { get; set; }
    
        [Parameter]
        public EventCallback<BlazorChatMessage> OnButtonClick { get; set; }
    
        string GetMessageClasses(BlazorChatMessage message) {
            switch (message.Role) {
                case ChatMessageRole.Assistant:
                    return "dxbl-chatui-message dxbl-chatui-message-assistant";
                case ChatMessageRole.User:
                    return "dxbl-chatui-message dxbl-chatui-message-user";
                case ChatMessageRole.Error:
                    return "dxbl-chatui-message dxbl-chatui-message-error";
                default:
                    return "dxbl-chatui-message";
            }
        }
    
        async Task OnButtonClicked() {
            if (OnButtonClick.HasDelegate)
                await OnButtonClick.InvokeAsync(message);
        }
    }
    

    Prompt Suggestion Template

    Use the SetPromptSuggestionContentTemplate(RenderFragment<IPromptSuggestion> template) method to customize prompt suggestions.

    Empty Message Area Template

    Use the SetEmptyMessageAreaTemplate(RenderFragment template) method to customize the UI when the chat has no message history.

    Customize Empty Message Area - WinForms Chat Control, DevExpress

    aiChatControl1.SetEmptyMessageAreaTemplate(builder => {
        builder.OpenComponent<EmptyArea>(0);
        builder.CloseComponent();
    });
    

    The EmptyArea.razor file:

    <style>
        .demo-chat-ui-description {
            font-weight: bold;
            font-size: 20px;
            text-align: center;
        }
    </style>
    
    <div class="demo-chat-ui-description">
        AI Assistant is ready to answer your questions.
    </div>
    

    Handle Chat Messages

    To manually process messages sent to an AI service, handle the MessageSent event. For example, you can manually call the AI client or service of choice and return its responses to the chat. The following example adds responses to user questions:

    using DevExpress.AIIntegration.Blazor.Chat.WebView;
    using Microsoft.Extensions.AI;
    
    public Chat() {
        InitializeComponent();
        aiChatControl1.MessageSent += AiChatControl1_MessageSent;
    }
    
    async void AiChatControl1_MessageSent(object sender, AIChatControlMessageSentEventArgs e) {
        await e.SendMessage($"Processed: {e.Content}", ChatRole.Assistant);
    }
    

    Save and Load Chat History

    Use the following methods to manage chat history:

    • SaveMessages – Returns an IEnumerable<ChatMessage> collection of messages.
    • LoadMessages – Loads messages from the specified IEnumerable<ChatMessage> collection to the AI Chat Control and refreshes the control.

    The following example saves/loads chat history when the user clicks the Save/Load button:

    public partial class Chat : XtraForm {
        List<BlazorChatMessage> chatHistory;
        public Chat() {
            InitializeComponent();
            buttonSave.Click += ButtonSave_Click;
            buttonLoad.Click += ButtonLoad_Click;
        }
    
        void ButtonSave_Click(object sender, EventArgs e) {
            chatHistory = (List<BlazorChatMessage>)aiChatControl1.SaveMessages();
        }
    
        void ButtonLoad_Click(object sender, EventArgs e) {
            if(chatHistory != null)
                aiChatControl1.LoadMessages(chatHistory);
        }
    }
    

    Create an Assistant That Chats Using Your Own Data

    When integrating the AI Chat Control with the OpenAI Assistant API, you can configure it to retain and reference a specific context (for example, a text file or a PDF document). By providing a supplementary document as a context source, the assistant is primed with background information. OpenAI automatically parses the document and searches through it to retrieve relevant content to better respond to user queries.

    1. Install the DevExpress.AIIntegration.OpenAI NuGet package.
    2. Create an assistant.

      using OpenAI;
      using OpenAI.Assistants;
      using OpenAI.Files;
      using System.IO;
      using System.ClientModel;
      using System.Threading.Tasks;
      using System.Threading;
      using System;
      
      #pragma warning disable OPENAI001
      public class OpenAIAssistantCreator {
          readonly AssistantClient assistantClient;
          readonly OpenAIFileClient fileClient;
          readonly string deployment;
      
          public OpenAIAssistantCreator(OpenAIClient client, string deployment) {
              assistantClient = client.GetAssistantClient();
              fileClient = client.GetOpenAIFileClient();
              this.deployment = deployment;
          }
      
          public async Task<string> CreateAssistantAsync(
                  Stream data,
                  string fileName,
                  string instructions,
                  bool useFileSearchTool = true,
                  CancellationToken ct = default) {
      
              data.Position = 0;
      
              ClientResult<OpenAIFile> fileResponse = await fileClient.UploadFileAsync(data, fileName, FileUploadPurpose.Assistants, ct);
              OpenAIFile file = fileResponse.Value;
      
              var resources = new ToolResources() {
                  CodeInterpreter = new CodeInterpreterToolResources(),
                  FileSearch = useFileSearchTool ? new FileSearchToolResources() : null
              };
              resources.FileSearch?.NewVectorStores.Add(new VectorStoreCreationHelper([file.Id]));
              resources.CodeInterpreter.FileIds.Add(file.Id);
      
              AssistantCreationOptions assistantCreationOptions = new AssistantCreationOptions() {
                  Name = Guid.NewGuid().ToString(),
                  Instructions = instructions,
                  ToolResources = resources
              };
      
              assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition());
              if (useFileSearchTool) {
                  assistantCreationOptions.Tools.Add(new FileSearchToolDefinition());
              }
      
              ClientResult<Assistant> assistantResponse = await assistantClient.CreateAssistantAsync(deployment, assistantCreationOptions, ct);
              Assistant assistant = assistantResponse.Value;
      
              return assistant.Id;
          }
      }
      #pragma warning restore OPENAI001
      

      Warning

      OpenAI.OpenAIClient API is for evaluation purposes only and is subject to change or removal in future updates. The code snippet suppresses the OPENAI001 diagnostic.

    3. Register the OpenAI Assistant service:

      using Azure.AI.OpenAI;
      using System.Windows.Forms;
      using System.ClientModel;
      using DevExpress.AIIntegration;
      using Microsoft.Extensions.AI;
      
      namespace AIChat {
          internal static class Program {
              [STAThread]
              static void Main() {
                  Application.EnableVisualStyles();
                  Application.SetCompatibleTextRenderingDefault(false);
      
                  var azureOpenAiClient = new Azure.AI.OpenAI.AzureOpenAIClient(AzureOpenAIEndpoint, AzureOpenAIKey);
      
                  var container = AIExtensionsContainerDesktop.Default;
                  container.RegisterChatClient(azureOpenAiClient.GetChatClient(ModelId).AsIChatClient());
                  container.RegisterOpenAIAssistants(azureOpenAiClient, ModelId); // For example, ModelId = "gpt-4o-mini"
      
                  Application.Run(new ChatForm(new OpenAIAssistantCreator(azureChatClient, ModelId)));
              }
              private static Uri AzureOpenAIEndpoint = new Uri("YOUR_AZURE_OPENAI_ENDPOINT");
              private static ApiKeyCredential AzureOpenAIKey = new ApiKeyCredential("YOUR_AZURE_OPENAI_KEY");
          }
      }
      

      Note

      Availability of Azure Open AI Assistants depends on the region. Refer to the following article for more information: Assistants (Preview).

    4. Handle the Initialized event with an async handler. Call the e.SetupAssistantAsync method to supply the Open AI Assistant identifier:

      using System.IO;
      using DevExpress.AIIntegration.Blazor.Chat.WebView;
      
      public partial class ChatForm : XtraForm {
          FileStream dataStream;
          readonly OpenAIAssistantCreator _openAIAssistantCreator;
      
          public ChatForm(OpenAIAssistantCreator openAIAssistantCreator) {
              InitializeComponent();
              _openAIAssistantCreator = openAIAssistantCreator;
              dataStream = File.OpenRead(@"RestaurantMenu.pdf");
              /*
               * In this example, aiChatControl1 was created and configured at design time.
               */
              aiChatControl1.Initialized += AiChatControl1_Initialized;
          }
          async void AiChatControl1_Initialized(object sender, AIChatControlInitializedEventArgs e) {
              string fileName = "RestaurantMenu.pdf";
              string prompt = "You are an Analyst Assistant specializing in PDF file analysis. Your role is to assist users by providing accurate answers to their questions about data contained in these files.";
              string assistantID = await _openAIAssistantCreator.CreateAssistantAsync(dataStream, fileName, prompt);
              await e.SetupAssistantAsync(assistantID); 
          }
      }
      

    The following screenshot shows the result:

    AI Assistant - WinForms AI Chat Control, DevExpress

    Tip

    The AI Chat Control does not render citation references as clickable links. These references appear as plain text (for example, [6:2†source]) in the output. To remove citation references from the AI response before display, handle the AI Chat Control’s MarkdownConvert event:

    using Markdig;
    using System.Text.RegularExpressions;
    
    void AiChatControl1_MarkdownConvert(object sender, AIChatControlMarkdownConvertEventArgs e) {
        string output = ClearCitationReferences(e.MarkdownText);
        e.HtmlText = (MarkupString)Markdown.ToHtml(output);
    }
    
    string ClearCitationReferences(string text) {
        return Regex.Replace(text, @"\【.*?】", "");
    }
    

    Troubleshooting

    Deploy to Windows Server

    When deploying WinForms applications with the AI Chat Control to Windows Server or earlier versions of Windows, you may encounter the following error:

    Warning

    Microsoft.Web.WebView2.Core.WebView2RuntimeNotFoundException: ‘Could not find a compatible WebView2 Runtime installation to host WebViews.’

    The WinForms AI Chat Control leverages BlazorWebView to reuse the DevExpress Blazor DxAIChat component. This integration requires the WebView2 runtime to be installed on the target machine.

    Windows 11 includes WebView2. However, earlier versions of Windows and Windows Server may not have it pre-installed. To ensure compatibility, refer to the following help topic for information on distributing the WebView2 runtime with your WinForms application to operating systems other than Windows 11: Distribute your app and the WebView2 Runtime.

    See Also