Showing different message in multilanguage dynamically - wpf

I have successfully create a multilanguage application based from here. I was great when able to change language.
Now I have stuck in this situation. My application is operating with hardware. So there is one screen that having interaction with hardware and display status textblock. The message will be variant depend on the response from hardware e.g. "Please wait..", "Scan your ID into scanner", "Scan complete", "Profile identified, continue with transaction".
How do this variant can be display in multilingual into single textblock?
Assuming the textblock will be naming TbxStatus.Text. How do I set the message in ResourceDictionary file and how do I handle which resource string key that it should take?
EDITED [WHAT HAVE I TRIED]
This is the code that I've write to switch language and show based from resources dictionary:-
App.cs
public static String Directory;
public static App Instance;
public static event EventHandler LanguageChangedEvent;
public App()
{
// Initialize static variables
Instance = this;
Directory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
Instance.SetLanguageResourceDictionary(Instance.GetLocXAMLFilePath("en-US"));
}
public static void LoadLanguageLocalization()
{
try
{
ViewModel.AppConfigViewModel.LocalizationProperty.LangLoc = new List<ApplicationModel.LanguageLocalization>
{
new ApplicationModel.LanguageLocalization { LanguageID = 1, CountryCode = "ms-MY", LanguageName = "Bahasa Malaysia" },
new ApplicationModel.LanguageLocalization { LanguageID = 2, CountryCode = "en-US", LanguageName = "English" },
new ApplicationModel.LanguageLocalization { LanguageID = 3, CountryCode = "zh-CN", LanguageName = "Chinese" },
new ApplicationModel.LanguageLocalization { LanguageID = 4, CountryCode = "ta-IN", LanguageName = "Tamil" }
};
}
catch (Exception ex)
{
LogEvents($"[App] Exception on LoadLanguageLocalization. Message-{ex.Message}. Stack Trace-{ex.StackTrace}", EventLogEntryType.Error);
ViewModel.AppConfigViewModel.LocalizationProperty.LangLoc = null;
}
}
public void SwitchLanguage(string inFiveCharLang)
{
if (System.Globalization.CultureInfo.CurrentCulture.Name.Equals(inFiveCharLang))
return;
var ci = new System.Globalization.CultureInfo(inFiveCharLang);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
SetLanguageResourceDictionary(GetLocXAMLFilePath(inFiveCharLang));
LanguageChangedEvent?.Invoke(this, new EventArgs());
}
private string GetLocXAMLFilePath(string inFiveCharLang)
{
string locXamlFile = "Resources." + inFiveCharLang + ".xaml";
return Path.Combine(Directory, "Language", locXamlFile);
}
public void SetLanguageResourceDictionary(String inFile)
{
if (File.Exists(inFile))
{
// Read in ResourceDictionary File
var languageDictionary = new ResourceDictionary();
languageDictionary.Source = new Uri(inFile);
// Remove any previous Localization dictionaries loaded
int langDictId = -1;
for (int i = 0; i < Resources.MergedDictionaries.Count; i++)
{
var md = Resources.MergedDictionaries[i];
// Make sure your Localization ResourceDictionarys have the ResourceDictionaryName
// key and that it is set to a value starting with "Loc-".
if (md.Contains("LanguageDictionaryName"))
{
if (md["LanguageDictionaryName"].ToString().StartsWith("Lang-"))
{
langDictId = i;
break;
}
}
}
if (langDictId == -1)
{
// Add in newly loaded Resource Dictionary
Resources.MergedDictionaries.Add(languageDictionary);
}
else
{
// Replace the current langage dictionary with the new one
Resources.MergedDictionaries[langDictId] = languageDictionary;
}
}
}
SelectLanguage.cs
private async void Page_Loaded(object sender, RoutedEventArgs e)
{
try
{
App.LogEvents($"[{PageTitle}] Loaded: Select language", System.Diagnostics.EventLogEntryType.Information);
BindingToPropertyControl();
}
catch (System.Exception ex)
{
string error = $"[{PageTitle}] Exception on Page_Loaded. Message: {ex.Message}. StackTrace: {ex.StackTrace}";
App.LogEvents(error, System.Diagnostics.EventLogEntryType.Error);
}
}
private void BindingToPropertyControl()
{
try
{
if (ViewModel.AppConfigViewModel.LocalizationProperty.LangLoc != null)
{
LanguagePack.ItemsSource = ViewModel.AppConfigViewModel.LocalizationProperty.LangLoc;
}
}
catch (System.Exception ex)
{
string error = $"[{PageTitle}] Exception on BindingToPropertyControl. Message: {ex.Message}. StackTrace: {ex.StackTrace}";
App.LogEvents(error, System.Diagnostics.EventLogEntryType.Error);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
ScreenTimer.Stop();
Button btn = (Button)sender;
string LangCode = btn.Tag.ToString();
App.LogEvents($"[{PageTitle}] Selecting language: {LangCode}", System.Diagnostics.EventLogEntryType.Information);
App.Instance.SwitchLanguage(LangCode.ToString());
Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(delegate ()
{
NavigationService.Navigate(new Uri(ApplicationModel.NaviModel.NaviSelectOptions, UriKind.RelativeOrAbsolute));
}));
}
catch (System.Exception ex)
{
string error = $"[{PageTitle}] Exception on Button_Click. Message: {ex.Message}. StackTrace: {ex.StackTrace}";
App.LogEvents(error, System.Diagnostics.EventLogEntryType.Error);
}
}
SelectLanguage.xaml
<ScrollViewer x:Name="ScrollLanguage" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<WrapPanel Height="Auto" Width="{Binding ElementName=ScrollLanguage, Path=ViewportWidth}">
<ItemsControl Name="LanguagePack">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,20" VerticalAlignment="Stretch" Width="{Binding ElementName=ScrollLanguage, Path=ViewportWidth}">
<Button Click="Button_Click" Tag="{Binding CountryCode}" Content="{Binding LanguageName}" VerticalAlignment="Center" Height="150" FontSize="60" Background="#FF1A5C9E" BorderBrush="{x:Null}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</WrapPanel>
</ScrollViewer>
SelectOptions.xaml
<TextBlock x:Name="tbTitle" TextWrapping="Wrap" Text="{StaticResource ResourceKey=SelectMerchant_Title}" FontSize="100" TextAlignment="Center" Padding="0,0,0,50" Foreground="White"/>
<Button x:Name="btnEatIn" Content="{StaticResource ResourceKey=SelectMerchant_Opt1}" VerticalAlignment="Center" Height="150" FontSize="60" Background="#FF057A5A" BorderBrush="{x:Null}"/>
<Button x:Name="btnEatIn" Content="{StaticResource ResourceKey=SelectMerchant_Opt2}" VerticalAlignment="Center" Height="150" FontSize="60" Background="#FF057A5A" BorderBrush="{x:Null}"/>
Resources.en-US.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- The name of this ResourceDictionary. Should not be localized. -->
<sys:String x:Key="LanguageDictionaryName" Localization.Comments="$Content(DoNotLocalize)">Lang-en-US</sys:String>
<!-- Localization specific styles -->
<FlowDirection x:Key="FlowDirection_Default" Localization.Comments="$Content(DoNotLocalize)">LeftToRight</FlowDirection>
<!--<FlowDirection x:Key="FlowDirection_Reverse" Localization.Comments="$Content(DoNotLocalize)">RightToLeft</FlowDirection>-->
<!-- SELECT ORDER TYPE -->
<sys:String x:Key="SelectMerchant_Title">Self-Service Kiosk</sys:String>
<sys:String x:Key="SelectMerchant_Opt1">Register new applicant</sys:String>
<sys:String x:Key="SelectMerchant_Opt2">Meal Application</sys:String>
</ResourceDictionary>
Back to what I'm facing, I can show different language by using resource key, but how to I display message or status which is dynamically (not static) into the display in multi-language?
Example, on validation screen, I have one TextBlock and currently I'm subscribe the event raise from hardware. How to show the status based from language that has been selected?
.
<StackPanel VerticalAlignment="Top" Margin="120,180,120,0" Grid.Row="1">
<TextBlock x:Name="tbGeneralStatus" TextWrapping="Wrap" Text="Please wait..." TextAlignment="Center" FontSize="50" Foreground="Yellow"/>
</StackPanel>
tbGeneralStatus.Text will show "Please wait..", "Scan your ID into scanner", "Scan complete", "Profile identified, continue with transaction" from delegate event from Barcode Scanner class.

I think you need to look a little into MVVM to make things easier with WPF. It takes some effort at the beggining, but its absolutely worth. I thought you were stucked only on how to receive and translate the status, so I'll try to give more info after looking at the code.
A quick guidance, based on Enum localization but not verified in dept.
You need a viewmodel to act as datacontext of the window you want to update. It has to implement INotifyPropertyChange interface to update the status in live.
class YourWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _statusText = string.Empty;
public string StatusText
{
get { return _statusText; }
set
{
_statusText = value;
OnPropertyChanged("StatusText");
}
}
public void YourMessageHandler(Status newStatus)
{
StatusText = GetLocalizedStatusText(newStatus);
}
private string GetLocalizedStatusText(Status newStatus)
{
switch (newStatus)
{
case Status.Wait: return Resources.StatusWaiting;
case Status.Continue: return Resources.StatusContinue;
case Status.Scan: return Resources.StatusScanId;
default: return string.Empty;
}
}
}
public enum Status
{
Wait,
Scan,
Continue
}
To bind to your window, make it like this:
<Window.DataContext>
<local:YourWindowViewModel/>
</Window.DataContext>
and change your TextBlock control to bind to the StatusText on the viewmodel
<StackPanel VerticalAlignment="Top" Margin="120,180,120,0" Grid.Row="1">
<TextBlock TextWrapping="Wrap" Text="{Binding StatusText}" TextAlignment="Center" FontSize="50" Foreground="Yellow"/>
</StackPanel>
Note that as I don't know your delegate/msgHandler format, I have put a generic "YourMessageHandler" method which receive the changing status

Related

Custom WPF MessageBox does not return MessageBoxResult

This is the xaml "SidiMessageBoxWindow.xaml" file:
<Window
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" mc:Ignorable="d">
<Border>
<Grid x:Name="mainGrid" Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" Margin="0">
<TextBlock x:Name="TextBlock" TextWrapping="Wrap" Text="TextBlock" ScrollViewer.VerticalScrollBarVisibility="Auto" Height="Auto"/>
</ScrollViewer>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Height="Auto" Width="Auto" Margin="0,10,0,0">
<Button x:Name="btnCancel" Content="Cancel" Width="Auto" MinWidth="0" Height="30"
HorizontalAlignment="Right" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center"
Margin="0,0,45,0" Padding="4,0,4,0" BorderThickness="1" />
<Button x:Name="btnOk" Content="Ok" Width="Auto" MinWidth="40" Height=" 30"
HorizontalAlignment="Right" HorizontalContentAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center"
Margin="0" Padding="4,0,4,0" BorderThickness="1" />
</Grid>
</Grid>
</Border>
and here the "SidiMessageBox1" class
I can't use "messageBox.ShowDialog();" here because I have to use the class "NTWindow" which I don't have access to.
public class SidiMessageBox1 : SidiMessageBoxWindow1
{
public static MessageBoxResult Show(ChartControl chartControl, string text, MessageBoxButton buttons = MessageBoxButton.OK)
{
if (chartControl == null)
{
return MessageBoxResult.None;
}
var messageBox = CreateMessageBox(chartControl, text, buttons);
messageBox.Show();
return messageBox.MsgBoxResult;
}
private static SidiMessageBoxWindow1 CreateMessageBox(ChartControl chartControl, string text, MessageBoxButton buttons)
{
return new SidiMessageBoxWindow1(text, buttons)
{
Owner = chartControl.OwnerChart,
Foreground = Application.Current.TryFindResource("FontControlBrush") as SolidColorBrush
};
}
}
and here is the SidiMessageBoxWindow1 class
public class SidiMessageBoxWindow1 : NTWindow
{
private static readonly string xamlFilePath = Path.Combine(Globals.UserDataDir, #"bin\Custom\AddOns\Sidi\SidiMessageBoxWindow.xaml");
private string text;
private Button btnOk, btnCancel;
private TextBlock textBlock;
private MessageBoxButton buttons;
public SidiMessageBoxWindow1()
{
}
public SidiMessageBoxWindow1(string text, MessageBoxButton buttons)
{
this.text = text;
Caption = "SidiMessageBox";
Topmost = true;
MinHeight = 100;
MinWidth = 200;
ResizeMode = ResizeMode.NoResize;
SizeToContent = SizeToContent.WidthAndHeight;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
Content = LoadXaml(xamlFilePath);
Buttons = buttons;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
btnOk.Click -= OkButton_Click;
Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
btnCancel.Click -= CancelButton_Click;
Close();
}
private DependencyObject LoadXaml(string xmlFilePath)
{
Window page;
FileStream fs = new FileStream(xmlFilePath, FileMode.Open);
page = (Window)XamlReader.Load(fs);
btnOk = LogicalTreeHelper.FindLogicalNode(page, "btnOk") as Button;
btnCancel = LogicalTreeHelper.FindLogicalNode(page, "btnCancel") as Button;
textBlock = LogicalTreeHelper.FindLogicalNode(page, "TextBlock") as TextBlock;
textBlock.Text = text;
return page.Content as DependencyObject;
}
public MessageBoxButton Buttons
{
get
{
return buttons;
}
set
{
buttons = value;
btnCancel.Visibility = Visibility.Collapsed;
btnOk.Visibility = Visibility.Collapsed;
switch (buttons)
{
case MessageBoxButton.OK:
btnOk.Visibility = Visibility.Visible;
btnOk.Click += OkButton_Click;
break;
case MessageBoxButton.OKCancel:
btnCancel.Visibility = Visibility.Visible;
btnOk.Visibility = Visibility.Visible;
btnOk.Click += OkButton_Click;
btnCancel.Click += CancelButton_Click;
break;
}
}
}
public MessageBoxResult MsgBoxResult { get; set; }
}
call:
var result = SidiMessageBox1.Show(ChartControl, "text");
the messageboxwindow looks like this:
everything works fine, as it should, except that I don't get a "MessageBoxResult" back. Unfortunately I don't know how to do that with this code.
I thank "BionicCode" for his explanation and hope for your understanding, because i am still quite a beginner ;-)
To use the Thread class is considered an obsolete programming model. Since the introduction of async and await with .NET Framework 4.5 the recommended programming model is the Microsoft Docs: Task asynchronous programming model.
As the member name Dispatcher.InvokeAsync suggests, this method is awaitable and supports asynchronous execution.
Your code actually does not execute the Window on a new thread. Because you use post related code to the Dispatcher, the Window is shown on the main thread.
Showing another Window will not block the other Window instances.
Additionally, your posted code is quite smelly. You should never block a constructor. But showing a modal dialog from a constructor will block construction. A constructor must initialize the members and return immediately.
Instead you must create and show the Window instance from your static Show method:
public static MessageBoxResult Show(ChartControl chartControl, string text, MessageBoxButton buttons)
{
if (chartControl == null)
{
return MessageBoxResult.None;
}
SidiMessageBoxWindow messageBox = CreateMessageBox(chartControl, text, buttons);
// If just an OK button, allow the user to just move away from the dialog
if (buttons == MessageBoxButton.OK)
{
messageBox.Show();
}
else
{
messageBox.ShowDialog();
}
return messageBox.MsgBoxResult;
}
private static SidiMessageBoxWindow CreateMessageBox(ChartControl chartControl, string text, MessageBoxButton buttons)
{
return new SidiMessageBoxWindow(xamlFilePath, logFilePath, text, buttons, chartControl)
{
Owner = chartControl.OwnerChart,
Foreground = Application.Current.TryFindResource("FontControlBrush") as SolidColorBrush
};
}
private SidiMessageBoxWindow(ChartControl chartControl, string text, MessageBoxButton buttons)
{
this.chartControl = chartControl;
this.text = text;
this.buttons = buttons;
}

APPCRASH on wpfgfx_v0400.dll only on 32-bit systems

I have an issue with a WPF application that I'm writing. I have a window that I load with profile pictures for the user to choose from when setting up an account within the application. Each picture is loaded into a user control, and placed in a stackpanel, so that when the user clicks the picture, it triggers the code in the user control, and automatically sets their profile picture without any more clicking needed. This window loads on 64-bit systems just fine. However, when loading on a 32-bit system, the entire application crashes. The faulting module is wpfgfx_v0400.dll. I don't know why it's crashing. Please help.
Here's the error in the Event Viewer:
Here's the XAML on the frontend of the window in question:
<Window x:Class="RandomApplication.Windows.ChooseProfilePic"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RandomApplication.Windows"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
ContentRendered ="On_ContentRendered"
Title="Choose A Picture" Height="515" Width="500" Background="Black" ResizeMode="CanMinimize">
<Grid Background="Black">
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" Height="420" VerticalAlignment="Top" Margin="0,5,0,0">
<StackPanel>
<StackPanel Name="HeadsPanel" Orientation="Horizontal" Height="100" VerticalAlignment="Top"/>
<StackPanel Name="AbstractPanel" Orientation="Horizontal" Height="100" VerticalAlignment="Top"/>
<StackPanel Name="ShapesPanel" Orientation="Horizontal" Height="100" VerticalAlignment="Top"/>
<StackPanel Name="MiscPanel" Orientation="Horizontal" Height="100" VerticalAlignment="Top"/>
</StackPanel>
</ScrollViewer>
<Button Name="CancelButton" VerticalAlignment="Bottom" Style="{DynamicResource RedButton}" Click="CancelButton_Click">Cancel</Button>
<Border Name="LoadingBorder" Background="Black">
<TextBlock Name="LoadingLabel" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="0,150" FontSize="20" FontWeight="Bold">
<Run>Loading Pictures</Run>
<LineBreak></LineBreak>
<Run>Please Wait...</Run>
</TextBlock>
</Border>
</Grid>
Here's the code behind the window:
namespace RandomApplication.Windows
{
public partial class ChooseProfilePic : Window
{
private readonly BackgroundWorker _loadPictureWorker = new BackgroundWorker();
public ChooseProfilePic()
{
InitializeComponent();
Topmost = true;
_loadPictureWorker.DoWork += LoadImages;
_loadPictureWorker.RunWorkerCompleted += LoadImages_Completed;
}
private void On_ContentRendered(object sender, EventArgs e)
{
_loadPictureWorker.RunWorkerAsync();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void LoadImages(object sender, DoWorkEventArgs e)
{
try
{
var headsImagePath = AppDomain.CurrentDomain.BaseDirectory + #"Images\Profile Pics\Heads\";
var abstractImagePath = AppDomain.CurrentDomain.BaseDirectory + #"Images\Profile Pics\Abstract\";
var shapesImagePath = AppDomain.CurrentDomain.BaseDirectory + #"Images\Profile Pics\Shapes\";
var miscImagePath = AppDomain.CurrentDomain.BaseDirectory + #"Images\Profile Pics\Misc\";
List<string> headsImageList = GetImages(headsImagePath);
List<string> abstractImageList = GetImages(abstractImagePath);
List<string> shapesImageList = GetImages(shapesImagePath);
List<string> miscImageList = GetImages(miscImagePath);
Application.Current.Dispatcher.Invoke(() =>
{
LoadViewingPanel(headsImageList, HeadsPanel);
LoadViewingPanel(abstractImageList, AbstractPanel);
LoadViewingPanel(shapesImageList, ShapesPanel);
LoadViewingPanel(miscImageList, MiscPanel);
});
}
catch (Exception ex)
{
CustomMessageBox.Show("Could not load images. :-(", "Image Retrieval Failed", MessageBoxButton.OK,
MessageBoxImage.Error);
Helper.WriteException(Helper.ErrorLogs + "Error Loading Images.txt", ex);
}
}
private void LoadImages_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
CustomMessageBox.Show("Could not load images. :-(", "Image Retrieval Failed", MessageBoxButton.OK,
MessageBoxImage.Error);
Helper.WriteException(Helper.ErrorLogs + "Error Loading Images.txt", e.Error);
}
else LoadingBorder.Visibility = Visibility.Hidden;
}
public List<string> GetImages(string imagePath)
{
var that = GetAllFiles(imagePath);
return that.ToList();
}
private void LoadViewingPanel(List<string> list, StackPanel panel)
{
foreach (var imageString in list)
{
Helper.WriteLineToFile(Helper.ErrorLogs + "2nd Info Loading Images.txt", imageString);
var thisUri = new Uri(imageString, UriKind.RelativeOrAbsolute);
var pic = new ProfilePic {ProfilePicImage = {Source = new BitmapImage(thisUri)}};
panel.Children.Add(pic);
}
}
private IEnumerable<string> GetAllFiles(string path)
{
return Directory.EnumerateFiles(path, "*.jpg").Union(
Directory.EnumerateDirectories(path).SelectMany(d =>
{
try
{
return GetAllFiles(d);
}
catch
{
return Enumerable.Empty<string>();
}
}));
}
}
}
I've researched what could cause issues with this particular dll, but none of it seems to relate to my issue.
So, I figured out the issue. Apparently, the size of the images that I was trying to load was too big. The images were all 2048x2048 pixels, which made them anywhere from 180 KB to 380 KB in size. Apparently this is too much. I resized all of the pictures to 100x100 pixels (as I was only ever presenting them to the user as 100x100), which brought the file sizes down to 7 - 10 KB each. After that, they loaded just fine with no crashing issues.

Can anyone provide a concrete example of WPF "visual inheritance" for a dialog box?

I am an experienced WinForms developer, relatively new to WPF. I have a large WinForms application that uses a couple different base classes to represent dialog boxes. One such example is AbstractOkCancelDialog. That class contains a panel at the bottom of a dialog, with an Ok and Cancel button on the right side of the panel. I'm trying to determine the best way to handle this, as I realize that WPF doesn't provide visual inheritance.
I don't want to have to create OK and Cancel buttons, and place them, for every dialog in the application.
I have read that the way to do this in WPF is with user controls. I can envision creating a user control with OK and Cancel buttons on it. But I don't want to have to manually place that user control on hundreds of dialogs in my application. I'd really like to have something like this:
public AbstractOkCancelDialog = class(Window)
{
protected AbstractOkCancelDialogViewModel _ViewModel;
// AbstractOkCancelDialogViewModel would have commands for OK and Cancel.
// Every dialog would inherit from AbstractOkCancelDialog, and would use
// a viewmodel that inherits from AbstractOkCancelDialogViewModel. In
// this way, all view models would automatically be connected to the OK
// and Cancel commands.
}
I've seen some discussion online about how to create the base class. Those discussions explain how there can't be a xaml file associated with the dialog base class, and I understand that restriction. I just can't figure out how to automatically place the user control with the OK and Cancel buttons.
I'm hoping that someone can point me to a sample solution that shows this kind of structure. Thank you in advance!
Write one dialog class. It's a subclass of Window. It has XAML:
<Window
...blah blah blah...
Title="{Binding Title}"
>
<StackPanel MinWidth="300">
<!-- This is how you place content within content in WPF -->
<ContentControl
Content="{Binding}"
Margin="2"
/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,20,2,2">
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="OK"
Click="OK_Click"
IsDefault="True"
/>
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="Cancel"
IsCancel="True"
Click="Cancel_Click"
/>
</StackPanel>
</StackPanel>
</Window>
You can fancy that up endlessly, but this is a decent minimum to give you arbitrary content above a row of right-aligned buttons. Adding more buttons as needed could involve either templating that portion of the window as well, or creating them with an ItemsControl (I've done that in our production code), or a few other options.
Usage:
var vm = new SomeDialogViewModel();
var dlg = new MyDialog { DataContext = vm };
For each dialog viewmodel, consumers must define an implicit datatemplate which provides UI for that viewmodel.
I would suggest writing a dialog viewmodel interface which the consumer is expected to implement.
public interface IDialogViewModel
{
String Title { get; set; }
void OnOK();
// Let them "cancel the cancel" if they like.
bool OnCancel();
}
The window can check if its DataContext implements that interface, and act accordingly. If you like, it could require that interface and throw an exception of it isn't implemented, or it could just talk to it only if it's there. If they don't implement it but they still have a Title property, the binding to Title will still work. Bindings are "duck typed".
Naturally, you can write an OKCancelDialogViewModel or a SelectStringFromListViewModel, write corresponding DataTemplates that implement their UIs, and write nice clean static methods which show them:
public static class Dialogs
{
public static TOption Select<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : class
{
// Viewmodel isn't generic because that breaks implicit datatemplating.
// That's OK because XAML uses duck typing anyhow.
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
Options = options
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return vm.SelectedOption as TOption;
}
return null;
}
// We have to call the value-type overload by a different name because overloads can't be
// distinguished when the only distinction is a type constraint.
public static TOption? SelectValue<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : struct
{
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
// Need to box these explicitly
Options = options.Select(opt => (object)opt)
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return (TOption)vm.SelectedOption;
}
return null;
}
}
Here's a viewmodel datatemplate for the above selection dialog:
<Application.Resources>
<DataTemplate DataType="{x:Type local:SelectOptionDialogViewModel}">
<StackPanel>
<TextBlock
TextWrapping="WrapWithOverflow"
Text="{Binding Prompt}"
/>
<ListBox
ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption}"
MouseDoubleClick="ListBox_MouseDoubleClick"
/>
</StackPanel>
</DataTemplate>
</Application.Resources>
App.xaml.cs
private void ListBox_MouseDoubleClick(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
((sender as FrameworkElement).DataContext as IDialogViewModel).DialogResult = true;
}
var a = Dialogs.Select(new String[] { "Bob", "Fred", "Ginger", "Mary Anne" },
"Select a dance partner:");
var b = Dialogs.SelectValue(Enum.GetValues(typeof(Options)).Cast<Options>(),
"Select an enum value:");
Here an example of how to use a custom AlertDialog
UserControl
<UserControl x:Class="Library.Views.AlertMessageDialogView"
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:Library.Properties"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
FlowDirection = "{Binding WindowFlowDirection, Mode=TwoWay}">
<Grid Background="{DynamicResource WindowBackgroundBrush}">
<Canvas HorizontalAlignment="Left" Height="145" VerticalAlignment="Top" Width="385">
<Label HorizontalAlignment="Left" Height="57" VerticalAlignment="Top" Width="365" Canvas.Left="10" Canvas.Top="10" FontSize="14" >
<TextBlock x:Name="txtVocabAnglais" TextWrapping="Wrap" Text="{Binding Message, Mode=TwoWay}" Width="365" Height="57" />
</Label>
<Button x:Name="cmdCancel" Content="{x:Static p:Resources.AlertMessageDialogViewcmdCancel}" Height="30" Canvas.Left="163" Canvas.Top="72" Width="71" Command = "{Binding CancelCommand}" CommandParameter = "null" IsDefault="True"/>
</Canvas>
</Grid>
</UserControl>
ViewModel Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
namespace Library.ViewModel
{
public class AlertMessageDialogViewModel : BindableBaseViewModel
{
public event EventHandler CloseWindowEvent;
private string _title;
private string _message;
public BaseCommand<string> YesCommand { get; private set; }
public BaseCommand<string> CancelCommand { get; private set; }
private WinformsNameSpace.FlowDirection _windowFlowDirection;
public AlertMessageDialogViewModel()
{
CancelCommand = new BaseCommand<string>(cmdCancelBtnClick);
WindowFlowDirection = CustomFuncVar.WindowFlowDirection;
}
public WinformsNameSpace.FlowDirection WindowFlowDirection
{
get
{
return _windowFlowDirection;
}
set
{
_windowFlowDirection = value;
OnPropertyChanged("WindowFlowDirection");
}
}
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public string Title
{
get
{
return _title;
}
set
{
_title = value;
}
}
private void cmdCancelBtnClick(string paramerter)
{
if (CloseWindowEvent != null)
CloseWindowEvent(this, null);
}
}
}
DialogMessage Class
using System;
using System.Windows;
using System.Collections.Generic;
namespace Library.Helpers
{
public static class DialogMessage
{
public static void AlertMessage(string message, string title, Window OwnerWindowView)
{
try
{
//Affichage de méssage de succès enregistrement
AlertMessageDialogViewModel alertDialogVM = new AlertMessageDialogViewModel();
alertDialogVM.Message = message;
alertDialogVM.Title = title;
// Auto Generation Window
FrameworkElement view = LpgetCustomUI.AutoGetViewFromName("AlertMessageDialogView");
view.DataContext = alertDialogVM;
Dictionary<string, object> localVarWindowProperty = new Dictionary<string, object>();
localVarWindowProperty = LpgetCustomUI.GetWindowPropretyType_400x145(Properties.Resources.ApplicationTitle);
CummonUIWindowContainer alertDialogView = new CummonUIWindowContainer(view, null, false, localVarWindowProperty);
//End Auto Generation Window
// Attachement de l'évènement de fermture de View au modèle
alertDialogVM.CloseWindowEvent += new EventHandler(alertDialogView.fnCloseWindowEvent);
if (OwnerWindowView!=null)
{
alertDialogView.Owner = OwnerWindowView;
}
else
{
alertDialogView.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
alertDialogView.ShowDialog();
}
catch (Exception ex)
{
}
}
}
}
CummonUIWindowContainer Class
namespace CummonUILibrary.CummonUIHelpers
{
public class CummonUIWindowContainer : Window
{
public event RoutedEventHandler CmbRootEvtLanguageChange;
private FrameworkElement currentView;
private ContentControl _contentcontainer;
public CummonUIWindowContainer(string usercontrolName)
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer()
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
Contentcontainer = new ContentControl();
Contentcontainer.Name = "ContentControl";
SetWindowProperty(view, model, setDataContextToView, WindowPropertyList);
}
public void SetWindowProperty(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
try
{
LinearGradientBrush brush = new LinearGradientBrush();
GradientStop gradientStop1 = new GradientStop();
gradientStop1.Offset = 0;
gradientStop1.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop1);
GradientStop gradientStop2 = new GradientStop();
gradientStop2.Offset = 0.5;
gradientStop2.Color = Colors.Indigo;
brush.GradientStops.Add(gradientStop2);
GradientStop gradientStop3 = new GradientStop();
gradientStop3.Offset = 1;
gradientStop3.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop3);
this.Background = brush;
CurrentView = view;
Type elementType = this.GetType();
ICollection<string> WindowPropertyListNames = WindowPropertyList.Keys;
foreach (string propertyName in WindowPropertyListNames)
{
PropertyInfo property = elementType.GetProperty(propertyName);
property.SetValue(this, WindowPropertyList[propertyName]);
}
if (setDataContextToView == true & model != null)
{
CurrentView.DataContext = model;
}
if (CurrentView != null)
{
Contentcontainer.Content = CurrentView;
}
//Contentcontainer.Margin = new Thickness(0,0, 0, 0);
IAddChild container=this;
container.AddChild(Contentcontainer);
}
catch (Exception ex)
{
}
}
public void fnCloseWindowEvent(object sender, EventArgs e)
{
this.Close();
}
public ContentControl Contentcontainer
{
get
{
return _contentcontainer;
}
set
{
_contentcontainer = value;
}
}
public FrameworkElement CurrentView
{
get
{
return currentView;
}
set
{
if (this.currentView != value)
{
currentView = value;
//RaisePropertyChanged("CurrentView");
}
}
}
private void cmbLanguage_SelectionChanged(object sender, RoutedEventArgs e)
{
//CmbRootEvtLanguageChange(sender, e);
}
}
}
How to use the Class
DialogMessage.AlertMessage("My Custom Message", "My Custom Title Message");
Thats how i would do it
Create an abstract base class for your dialog and changing the corresponding ControlTemplate
AbstractOkCancelDialog
public abstract class AbstractOkCancelDialog : Window
{
public static readonly DependencyProperty CancelCommandParameterProperty =
DependencyProperty.Register(
"CancelCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty CancelCommandProperty =
DependencyProperty.Register(
"CancelCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
public static readonly DependencyProperty OkCommandParameterProperty =
DependencyProperty.Register(
"OkCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty OkCommandProperty =
DependencyProperty.Register(
"OkCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
static AbstractOkCancelDialog()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AbstractOkCancelDialog), new
FrameworkPropertyMetadata(typeof(AbstractOkCancelDialog)));
}
public ICommand CancelCommand
{
get => (ICommand) GetValue(CancelCommandProperty);
set => SetValue(CancelCommandProperty, value);
}
public object CancelCommandParameter
{
get => GetValue(CancelCommandParameterProperty);
set => SetValue(CancelCommandParameterProperty, value);
}
public ICommand OkCommand
{
get => (ICommand) GetValue(OkCommandProperty);
set => SetValue(OkCommandProperty, value);
}
public object OkCommandParameter
{
get => GetValue(OkCommandParameterProperty);
set => SetValue(OkCommandParameterProperty, value);
}
}
Style
Put in Generic.xaml[?]
<Style
BasedOn="{StaticResource {x:Type Window}}"
TargetType="{x:Type local:AbstractOkCancelDialog}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AbstractOkCancelDialog}">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Margin="5"
Command="{TemplateBinding OkCommand}"
CommandParameter="{TemplateBinding OkCommandParameter}"
Content="Ok"
DockPanel.Dock="Right" />
<Button
Grid.Column="2"
Margin="5"
Command="{TemplateBinding CancelCommand}"
CommandParameter="{TemplateBinding CancelCommandParameter}"
Content="Cancel"
DockPanel.Dock="Right" />
</Grid>
</Grid>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now you can create your individual dialogs like you would create any other window
Brief example:
TestDialog.xaml
<local:AbstractOkCancelDialog
x:Class="WpfApp.TestDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestDialog"
Width="800"
Height="450"
OkCommand="{x:Static local:Commands.OkWindowCommand}"
OkCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
CancelCommand="{x:Static local:Commands.CancelWindowCommand}"
CancelCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<Grid>
<!-- Content -->
</Grid>
</local:AbstractOkCancelDialog>
TestDialog.xaml.cs
public partial class TestDialog : AbstractOkCancelDialog
{
...
}

OnApplyTemplate is not being called

I have a solution with 2 projects: Windows Phone App and a Windows Phone Class Library. The class library has a control called MessageBoxExtended which inherits from ContentControl. The project also has a Themes folder with a generic.xaml file. The file has the Build Action set to Page and it looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:KontactU.Common.WPControls;assembly=KontactU.Common.WPControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Style TargetType="local:MessageBoxExtended">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MessageBoxExtended">
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBlock x:Name="lblTitle" Text="Title Goes Here" Style="{StaticResource PhoneTextTitle3Style}"/>
<TextBlock x:Name="lblMessage" Text="Some long message here repeated over and over again. Some long message here repeated over and over again. " TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="btnLeft" Content="Button1" Click="btnLeft_Click"></Button>
<Button x:Name="btnCenter" Content="Button2" Click="btnCenter_Click"></Button>
<Button x:Name="btnRight" Content="Button3" Click="btnRight_Click"></Button>
</StackPanel>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The control code looks like this:
public class MessageBoxExtended : ContentControl
{
private TextBlock lblTitle;
private TextBlock lblMessage;
private Button btnLeft;
private Button btnCenter;
private Button btnRight;
private bool currentSystemTrayState;
internal Popup ChildWindowPopup
{
get;
private set;
}
private static PhoneApplicationFrame RootVisual
{
get
{
return Application.Current == null ? null : Application.Current.RootVisual as PhoneApplicationFrame;
}
}
public MessageBoxExtended()
: base()
{
DefaultStyleKey = typeof(MessageBoxExtended);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
lblTitle = this.GetTemplateChild("lblTitle") as TextBlock;
lblMessage = this.GetTemplateChild("lblMessage") as TextBlock;
btnLeft = this.GetTemplateChild("btnLeft") as Button;
btnCenter = this.GetTemplateChild("btnCenter") as Button;
btnRight = this.GetTemplateChild("btnRight") as Button;
InitializeMessageBoxExtended("Title", "Message", MessageBoxExtendedButtonType.Ok);
}
private void InitializeMessageBoxExtended(string title, string message, MessageBoxExtendedButtonType buttonType)
{
HideSystemTray();
lblTitle.Text = title;
lblMessage.Text = message;
switch (buttonType)
{
case MessageBoxExtendedButtonType.Ok:
btnLeft.Visibility = System.Windows.Visibility.Collapsed;
btnRight.Visibility = System.Windows.Visibility.Collapsed;
btnCenter.Content = "ok";
break;
case MessageBoxExtendedButtonType.OkCancel:
btnCenter.Visibility = System.Windows.Visibility.Collapsed;
btnLeft.Content = "ok";
btnRight.Content = "cancel";
break;
case MessageBoxExtendedButtonType.YesNo:
btnCenter.Visibility = System.Windows.Visibility.Collapsed;
btnLeft.Content = "yes";
btnRight.Content = "no";
break;
}
}
public void Show(string title, string message, MessageBoxExtendedButtonType buttonType)
{
if (ChildWindowPopup == null)
{
ChildWindowPopup = new Popup();
try
{
ChildWindowPopup.Child = this;
}
catch (ArgumentException)
{
throw new InvalidOperationException("The control is already shown.");
}
}
if (ChildWindowPopup != null && Application.Current.RootVisual != null)
{
// Configure accordingly to the type
InitializeMessageBoxExtended(title, message, buttonType);
// Show popup
ChildWindowPopup.IsOpen = true;
}
}
private void HideSystemTray()
{
// Capture current state of the system tray
this.currentSystemTrayState = SystemTray.IsVisible;
// Hide it
SystemTray.IsVisible = false;
}
}
The Windows Phone App references it and calls it in the code behind by instantiating it and calling the Show method:
MessageBoxExtended mbe = new MessageBoxExtended();
mbe.Show();
The problem is that the OnApplyTemplate is never called. I've tried commenting out all the lines in the generic.xaml, but I get the same result.
Any ideas?
Never mind, it was my mistake. I added
if (lblTitle == null)
return;
to the InitializeMessageBoxExtended() method and now it works. If you follow the logic the constructor is called before the OnApplyTemplate() which calls the InitializeMessageBoxExtended() and therefore the values are null. By adding the code above the control doesn't throw an exception, it continues and when the control is part of the VisualTree the OnApplyTemplate is called.
Hope this helps anyone out there.

WPF: Create a dialog / prompt

I need to create a Dialog / Prompt including TextBox for user input. My problem is, how to get the text after having confirmed the dialog? Usually I would make a class for this which would save the text in a property. However I want do design the Dialog using XAML. So I would somehow have to extent the XAML Code to save the content of the TextBox in a property - but I guess that's not possible with pure XAML. What would be the best way to realize what I'd like to do? How to build a dialog which can be defined from XAML but can still somehow return the input? Thanks for any hint!
The "responsible" answer would be for me to suggest building a ViewModel for the dialog and use two-way databinding on the TextBox so that the ViewModel had some "ResponseText" property or what not. This is easy enough to do but probably overkill.
The pragmatic answer would be to just give your text box an x:Name so that it becomes a member and expose the text as a property in your code behind class like so:
<!-- Incredibly simplified XAML -->
<Window x:Class="MyDialog">
<StackPanel>
<TextBlock Text="Enter some text" />
<TextBox x:Name="ResponseTextBox" />
<Button Content="OK" Click="OKButton_Click" />
</StackPanel>
</Window>
Then in your code behind...
partial class MyDialog : Window {
public MyDialog() {
InitializeComponent();
}
public string ResponseText {
get { return ResponseTextBox.Text; }
set { ResponseTextBox.Text = value; }
}
private void OKButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
DialogResult = true;
}
}
Then to use it...
var dialog = new MyDialog();
if (dialog.ShowDialog() == true) {
MessageBox.Show("You said: " + dialog.ResponseText);
}
Edit: Can be installed with nuget https://www.nuget.org/packages/PromptDialog/
I just add a static method to call it like a MessageBox:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Class="utils.PromptDialog"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
SizeToContent="WidthAndHeight"
MinWidth="300"
MinHeight="100"
WindowStyle="SingleBorderWindow"
ResizeMode="CanMinimize">
<StackPanel Margin="5">
<TextBlock Name="txtQuestion" Margin="5"/>
<TextBox Name="txtResponse" Margin="5"/>
<PasswordBox Name="txtPasswordResponse" />
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right">
<Button Content="_Ok" IsDefault="True" Margin="5" Name="btnOk" Click="btnOk_Click" />
<Button Content="_Cancel" IsCancel="True" Margin="5" Name="btnCancel" Click="btnCancel_Click" />
</StackPanel>
</StackPanel>
</Window>
And the code behind:
public partial class PromptDialog : Window
{
public enum InputType
{
Text,
Password
}
private InputType _inputType = InputType.Text;
public PromptDialog(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(PromptDialog_Loaded);
txtQuestion.Text = question;
Title = title;
txtResponse.Text = defaultValue;
_inputType = inputType;
if (_inputType == InputType.Password)
txtResponse.Visibility = Visibility.Collapsed;
else
txtPasswordResponse.Visibility = Visibility.Collapsed;
}
void PromptDialog_Loaded(object sender, RoutedEventArgs e)
{
if (_inputType == InputType.Password)
txtPasswordResponse.Focus();
else
txtResponse.Focus();
}
public static string Prompt(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
{
PromptDialog inst = new PromptDialog(question, title, defaultValue, inputType);
inst.ShowDialog();
if (inst.DialogResult == true)
return inst.ResponseText;
return null;
}
public string ResponseText
{
get
{
if (_inputType == InputType.Password)
return txtPasswordResponse.Password;
else
return txtResponse.Text;
}
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
So you can call it like:
string repeatPassword = PromptDialog.Prompt("Repeat password", "Password confirm", inputType: PromptDialog.InputType.Password);
Great answer of Josh, all credit to him, I slightly modified it to this however:
MyDialog Xaml
<StackPanel Margin="5,5,5,5">
<TextBlock Name="TitleTextBox" Margin="0,0,0,10" />
<TextBox Name="InputTextBox" Padding="3,3,3,3" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Name="BtnOk" Content="OK" Grid.Column="0" Margin="0,0,5,0" Padding="8" Click="BtnOk_Click" />
<Button Name="BtnCancel" Content="Cancel" Grid.Column="1" Margin="5,0,0,0" Padding="8" Click="BtnCancel_Click" />
</Grid>
</StackPanel>
MyDialog Code Behind
public MyDialog()
{
InitializeComponent();
}
public MyDialog(string title,string input)
{
InitializeComponent();
TitleText = title;
InputText = input;
}
public string TitleText
{
get { return TitleTextBox.Text; }
set { TitleTextBox.Text = value; }
}
public string InputText
{
get { return InputTextBox.Text; }
set { InputTextBox.Text = value; }
}
public bool Canceled { get; set; }
private void BtnCancel_Click(object sender, System.Windows.RoutedEventArgs e)
{
Canceled = true;
Close();
}
private void BtnOk_Click(object sender, System.Windows.RoutedEventArgs e)
{
Canceled = false;
Close();
}
And call it somewhere else
var dialog = new MyDialog("test", "hello");
dialog.Show();
dialog.Closing += (sender,e) =>
{
var d = sender as MyDialog;
if(!d.Canceled)
MessageBox.Show(d.InputText);
}
You don't need ANY of these other fancy answers. Below is a simplistic example that doesn't have all the Margin, Height, Width properties set in the XAML, but should be enough to show how to get this done at a basic level.
XAML
Build a Window page like you would normally and add your fields to it, say a Label and TextBox control inside a StackPanel:
<StackPanel Orientation="Horizontal">
<Label Name="lblUser" Content="User Name:" />
<TextBox Name="txtUser" />
</StackPanel>
Then create a standard Button for Submission ("OK" or "Submit") and a "Cancel" button if you like:
<StackPanel Orientation="Horizontal">
<Button Name="btnSubmit" Click="btnSubmit_Click" Content="Submit" />
<Button Name="btnCancel" Click="btnCancel_Click" Content="Cancel" />
</StackPanel>
Code-Behind
You'll add the Click event handler functions in the code-behind, but when you go there, first, declare a public variable where you will store your textbox value:
public static string strUserName = String.Empty;
Then, for the event handler functions (right-click the Click function on the button XAML, select "Go To Definition", it will create it for you), you need a check to see if your box is empty. You store it in your variable if it is not, and close your window:
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if (!String.IsNullOrEmpty(txtUser.Text))
{
strUserName = txtUser.Text;
this.Close();
}
else
MessageBox.Show("Must provide a user name in the textbox.");
}
Calling It From Another Page
You're thinking, if I close my window with that this.Close() up there, my value is gone, right? NO!! I found this out from another site: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/
They had a similar example to this (I cleaned it up a bit) of how to open your Window from another and retrieve the values:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
{
MyPopupWindow popup = new MyPopupWindow(); // this is the class of your other page
//ShowDialog means you can't focus the parent window, only the popup
popup.ShowDialog(); //execution will block here in this method until the popup closes
string result = popup.strUserName;
UserNameTextBlock.Text = result; // should show what was input on the other page
}
}
Cancel Button
You're thinking, well what about that Cancel button, though? So we just add another public variable back in our pop-up window code-behind:
public static bool cancelled = false;
And let's include our btnCancel_Click event handler, and make one change to btnSubmit_Click:
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
cancelled = true;
strUserName = String.Empty;
this.Close();
}
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if (!String.IsNullOrEmpty(txtUser.Text))
{
strUserName = txtUser.Text;
cancelled = false; // <-- I add this in here, just in case
this.Close();
}
else
MessageBox.Show("Must provide a user name in the textbox.");
}
And then we just read that variable in our MainWindow btnOpenPopup_Click event:
private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
{
MyPopupWindow popup = new MyPopupWindow(); // this is the class of your other page
//ShowDialog means you can't focus the parent window, only the popup
popup.ShowDialog(); //execution will block here in this method until the popup closes
// **Here we find out if we cancelled or not**
if (popup.cancelled == true)
return;
else
{
string result = popup.strUserName;
UserNameTextBlock.Text = result; // should show what was input on the other page
}
}
Long response, but I wanted to show how easy this is using public static variables. No DialogResult, no returning values, nothing. Just open the window, store your values with the button events in the pop-up window, then retrieve them afterwards in the main window function.

Resources