Hello,
In the Sample Browser under User Prompt Quickstart, it shows a user prompt with an animated progress bar. However, everything is static. How can we make the progress of the progress bar and the text update dynamically?
Thanks.
Benjamin.
Hello,
In the Sample Browser under User Prompt Quickstart, it shows a user prompt with an animated progress bar. However, everything is static. How can we make the progress of the progress bar and the text update dynamically?
Thanks.
Benjamin.
Hi Benjamin,
Great question, and we've updated our sample for the next release to show exactly how to achieve that. The steps are essentially as follows:
Step 1: Cache UI control references that need to be updated
To update the progress bar and status while the prompt is open, the easiest approach is to store references to these controls in local variables that are later used while defining the content of the prompt. In our updated sample, we created the two controls with their default state and updated the "WithContent" method to use these instances when building the content.
Step 2: Update Controls from Background Thread
It is important to note that while the modal dialog is open, the calling thread is blocked. This means any work must be performed on a background thread. When updating the controls from the background thread, you must use "Dispatcher.InvokeAsync" to push the update to the UI thread without blocking the background thread.
In our sample, we used the "BeforeShow" callback to start running a Task. We used this callback because it passes an instance of the UserPromptBuilder that allows us to interact with the associated UserPromptControl that is displayed. We can then use the UserPromptControl.Result property to test if the prompt has been cancelled and optionally automatically assign a result when our work completes.
Updated Sample
Here's the full source of the updated sample that will be included in the next release
//
// SAMPLE: Customize the header and content
//
var statusText = new TextBlock() {
Text = "Estimated time remaining:",
Margin = new Thickness(0, 2, 0, 2)
};
var progressBar = new AnimatedProgressBar() {
Margin = new Thickness(0, 5, 0, 0),
Minimum = 0,
Maximum = 100,
Value = 0,
Height = 20,
State = OperationState.Normal
};
ConfigureUserPrompt(displayResult: true)
// Setting any header background will align the status icon and header content
.WithHeaderContent("Exporting Project (Sample Project)")
.WithHeaderForeground(Colors.White)
.WithHeaderBackground(
new LinearGradientBrush(
startColor: (Color)ColorConverter.ConvertFromString("#094C75"),
endColor: (Color)ColorConverter.ConvertFromString("#066F5C"),
angle: 0d)
)
.WithStatusImage(ImageLoader.GetIcon("Save32.png"))
.WithStandardButtons(UserPromptStandardButtons.Cancel)
.WithContent(new StackPanel() {
Children = {
new TextBlock() {
Inlines = {
new Run("to "),
new Run("Project Templates") { FontWeight = FontWeights.Bold },
new Run(@" (C:\Templates\ProjectTemplates)"),
}
},
statusText,
progressBar
}
})
.WithCheckBoxContent("Check this box to simulate an exception")
.WithWindowStartupLocation(WindowStartupLocation.CenterOwner)
.WithAutoSize(true, minimumWidth: 400)
.BeforeShow(builder => {
// Do work here
Task.Run(() => {
var totalTime = TimeSpan.FromSeconds(10);
var startTaskTime = DateTime.Now;
var isCompleted = false;
var throwException = false;
try {
do {
if (throwException)
throw new ApplicationException("An error was encountered during export.");
Thread.Sleep(100);
var elapsedTime = DateTime.Now - startTaskTime;
var remainingTime = totalTime - elapsedTime;
var percentageComplete = ((elapsedTime / totalTime) * 100);
Dispatcher.InvokeAsync(() => {
throwException = builder.Instance?.IsChecked == true;
if (!throwException) {
progressBar.Value = percentageComplete;
statusText.Text = $"Estimated time remaining: {remainingTime.TotalSeconds.Round(RoundMode.Ceiling)} seconds";
isCompleted = (progressBar.IsCompleted || (builder.Instance?.Result is not null));
}
});
} while (!isCompleted);
if (isCompleted) {
Dispatcher.InvokeAsync(() => {
if ((builder.Instance is not null) && (builder.Instance.Result is null))
builder.Instance.Result = builder.Instance.DefaultResult;
});
}
}
catch (Exception ex) {
Dispatcher.InvokeAsync(() => {
statusText.Text = "Error: " + ex.Message;
progressBar.State = OperationState.Error;
});
}
});
})
.Show();
Nice, thanks! It works except for the size. I tried setting the minimum size to 1000 and even disabled auto-size, but it still always uses the smallest possible size.
Can you please provide more information about what you are referring to in regards to size since the original post only mentioned static content? Are you trying to create a resizable window? From the same sample above, I commented out the following line that auto-sized the content:
.WithAutoSize(true, minimumWidth: 400)
And then I added the following line to configure the window to support resizing:
.WithCanResize(true)
With those two changes the window was resizable and the content would adjust to the size of the window. If you want the window to be resizable you do have to disable auto-sizing of the content. Content auto-sizing is performed once to determine the best width for the content and explicitly sets the content to that width. It is intended to be used with non-resizable windows that will be sized to fit their content. More details about reizable windows here.
Please note that improved support for resizable windows was added in v25.1.0.
I just want to change the minimum width, but it doesn’t work. After doing more research, I found out that the sizing works correctly in a WPF application but not in a VSTO add-in.
Here’s a repo I created with two projects using the same wide UserPrompt: as mentioned, it works in the WPF app but not in the VSTO add-in: ActiproUserPromptSize.
Also, in the VSTO add-in, if I don’t add .WithTitle("Title"), I get an exception saying the title is null.
Thanks.
Thank you for the additional information. Knowing that this is a VSTO scenario is a very important detail. What I suspect is happening is that since VSTO is not a WPF application the UserPromptBuilder is unable to properly resolve the "owner" window. That particular sample is also configured with this line:
.WithWindowStartupLocation(WindowStartupLocation.CenterOwner)
Since it was configured to center on the owner and no owner could be found, it was interferring with the measure logic.
I was able to reproduce that scenario in WPF if I manually cleared the owner in our internal code, so I believe that is the cause of what you are seeing in VSTO. You should be able to resolve this issue in one of two ways:
We are going to update our code for the next release so that if the startup location is configured as CenterOwner and no owner is available, it will force screen-based sizing.
Thanks, it works. I went with the first option, but removing the configuration for WindowStartupLocation wasn’t enough—I had to manually set WindowStartupLocation to CenterScreen for it to work.
I assume the title exception happens for the same reason: it tries to take the title from the owner, but the owner is null.
[Modified 2 months ago]
Title has different logic for resolving what to display when a title is not given. I assume in the VSTO scenario the issue is that there is not an EntryAssembly from which to resolve the TitleAttribute when none of the other values are defined. You can read more about the Title logic here. We'll update our logic to coerce null to an empty string to prevent the error. In the meantime, I suggest you configure UserPromptBuilder.FallbackTitle to an appropriate non-null value. Thank you for letting us know about the issue.
Please log in to a validated account to post comments.