C# WPF Keep dialog (child) window open if input is wrong - c#

I am looking for a solution to not close my dialog window when there is a duplicate ID. I guess it has to do with DialogResult, however, I cannot make neither true or false work.
I am opening the "New Store"-window from a button click in my main window.
NewStoreWindow newStore = new NewStoreWindow();
newStore.ShowDialog();
As you can see on the screenshots below, you can fill in the ID and the Name. I have successfully made an input validation, so you cannot press "OK" when the 2 textboxes are empty.
When you press "OK" and the ID is already in my listview (gridview), it will give the user the below error message. However, it will also close the "New Store"-window. As said, I would like that the window is kept open until it successfully is added, so that the user can just edit the ID instead of having to open the "New Store"-window again and typing it again.
It should only close the window when it is successfully added or cancel/X is pressed.
I have tried to play around with closing-event for "New Store", but it does not seem to work.
Is this just the designed behavior? And is there a way to bypass/work around it?
CS
public partial class NewStoreWindow : Window
{
public static bool itemAlreadyAdded;
//public MainWindow mainWin = new MainWindow();
public bool IsDefault { get; set; }
public NewStoreWindow()
{
InitializeComponent();
//SetButtonState();
}
// Data Binding
public static readonly DependencyProperty SidProperty = DependencyProperty.Register("Sid", typeof(string), typeof(NewStoreWindow), new UIPropertyMetadata(String.Empty));
public string Sid
{
get { return (string)GetValue(SidProperty); }
set { SetValue(SidProperty, value); }
}
// Data Binding
public static readonly DependencyProperty SNameProperty = DependencyProperty.Register("SName", typeof(string), typeof(NewStoreWindow), new UIPropertyMetadata(String.Empty));
public string SName
{
get { return (string)GetValue(SNameProperty); }
set { SetValue(SNameProperty, value); }
}
private void cmdOK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
MainWindow mainWin = new MainWindow();
// Check if any items exist in listview
if (MainWindow._list.Count == 0)
{
MainWindow._list.Add(new MainWindow.Data() { Sid = Sid, SName = SName });
}
else // items exist
{
itemAlreadyAdded = false; // use for boolean checking
foreach (var item in mainWin.lvStores.Items.OfType<MainWindow.Data>()) // loop through all items in listview
{
if (item.Sid == Sid) // Check if new item already exists
{
itemAlreadyAdded = true;
}
if (itemAlreadyAdded) // Show messagebox if it exists
{
MessageBox.Show("ID needs to be unique, please respecify!", "Duplicate ID",
MessageBoxButton.OK, MessageBoxImage.Error);
break;
}
}
if (!itemAlreadyAdded) // If it does not already exist, add it
{
// if it does not exist, create it from the textbox values
MainWindow._list.Add(new MainWindow.Data() { Sid = Sid, SName = SName });
// Refresh listview
mainWin.lvStores.Items.Refresh();
// Close Window
Close();
}
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
public void Close()
{
this.Closing -= Window_Closing;
base.Close();
}

Related

How to cancel tab change in WPF TabControl

I have found multiple questions about this problem on SO, however I still can't quite get a realiable solution. Here is what I came up with after reading the answers.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300" x:Name="this">
<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Tabs, ElementName=this}" x:Name="TabControl"/>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var tabs = new ObservableCollection<string> {"Tab1", "Tab2", "Tab3"};
Tabs = CollectionViewSource.GetDefaultView(tabs);
Tabs.CurrentChanging += OnCurrentChanging;
Tabs.CurrentChanged += OnCurrentChanged;
Tabs.MoveCurrentToFirst();
CurrentTab = tabs.First();
}
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
//only show message box when tab is changed by user input
if (!_cancelTabChange)
{
if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
_cancelTabChange = true;
return;
}
}
_cancelTabChange = false;
}
private void OnCurrentChanged(object sender, EventArgs e)
{
if (!_cancelTabChange)
{
//Update current tab property, if user did not cancel transition
CurrentTab = (string)Tabs.CurrentItem;
}
else
{
//navigate back to current tab otherwise
Dispatcher.BeginInvoke(new Action(() => Tabs.MoveCurrentTo(CurrentTab)));
}
}
public string CurrentTab { get; set; }
public static readonly DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow), new FrameworkPropertyMetadata(default(ICollectionView)));
public ICollectionView Tabs
{
get { return (ICollectionView)GetValue(TabsProperty); }
set { SetValue(TabsProperty, value); }
}
private bool _cancelTabChange;
}
Basically I want to display a confirmation message, when user navigates to different tab, and if he clicks "no" - abort the transition. This code does not work though. If you click multiple times on "Tab2", each time choosing "no" in message box, at some point it stops working: events stop triggering. Event will trigger again if you click on "Tab3", but if you choose "yes" it opens second tab and not third. I am having trouble figuring out wtf is going on. :)
Does anyone see a bug in my solution? Or is there an easier way to display a confirmation message, when user switches tabs? I am also willing to use any opensource tab control, which does have a proper SelectionChanging event. I could not find any though.
I am using .Net 4.0.
Edit:
If I comment the message box out:
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
//only show message box when tab is changed by user input
if (!_cancelTabChange)
{
//if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
//{
Debug.WriteLine("Canceled");
_cancelTabChange = true;
return;
//}
}
_cancelTabChange = false;
}
Everything works fine. Weird.
This solution http://coderelief.net/2011/11/07/fixing-issynchronizedwithcurrentitem-and-icollectionview-cancel-bug-with-an-attached-property/
seems to work quite well with
<TabControl ... yournamespace:SelectorAttachedProperties.IsSynchronizedWithCurrentItemFixEnabled="True" .../>
private void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (MessageBox.Show("Change tab?", "Message", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
e.Cancel = true;
}
}
public static class SelectorAttachedProperties
{
private static Type _ownerType = typeof(SelectorAttachedProperties);
#region IsSynchronizedWithCurrentItemFixEnabled
public static readonly DependencyProperty IsSynchronizedWithCurrentItemFixEnabledProperty =
DependencyProperty.RegisterAttached("IsSynchronizedWithCurrentItemFixEnabled", typeof(bool), _ownerType,
new PropertyMetadata(false, OnIsSynchronizedWithCurrentItemFixEnabledChanged));
public static bool GetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsSynchronizedWithCurrentItemFixEnabledProperty);
}
public static void SetIsSynchronizedWithCurrentItemFixEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsSynchronizedWithCurrentItemFixEnabledProperty, value);
}
private static void OnIsSynchronizedWithCurrentItemFixEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Selector selector = d as Selector;
if (selector == null || !(e.OldValue is bool && e.NewValue is bool) || e.OldValue == e.NewValue)
return;
bool enforceCurrentItemSync = (bool)e.NewValue;
ICollectionView collectionView = null;
EventHandler itemsSourceChangedHandler = null;
itemsSourceChangedHandler = delegate
{
collectionView = selector.ItemsSource as ICollectionView;
if (collectionView == null)
collectionView = CollectionViewSource.GetDefaultView(selector);
};
SelectionChangedEventHandler selectionChangedHanlder = null;
selectionChangedHanlder = delegate
{
if (collectionView == null)
return;
if (selector.IsSynchronizedWithCurrentItem == true && selector.SelectedItem != collectionView.CurrentItem)
{
selector.IsSynchronizedWithCurrentItem = false;
selector.SelectedItem = collectionView.CurrentItem;
selector.IsSynchronizedWithCurrentItem = true;
}
};
if (enforceCurrentItemSync)
{
TypeDescriptor.GetProperties(selector)["ItemsSource"].AddValueChanged(selector, itemsSourceChangedHandler);
selector.SelectionChanged += selectionChangedHanlder;
}
else
{
TypeDescriptor.GetProperties(selector)["ItemsSource"].RemoveValueChanged(selector, itemsSourceChangedHandler);
selector.SelectionChanged -= selectionChangedHanlder;
}
}
#endregion IsSynchronizedWithCurrentItemFixEnabled
}
For some reason adding TabControl.Focus() fixes things:
private void OnCurrentChanged(object sender, EventArgs e)
{
if (!_cancelTabChange)
{
//Update current tab property, if user did not cancel transition
CurrentTab = (string)Tabs.CurrentItem;
}
else
{
//navigate back to current tab otherwise
Dispatcher.BeginInvoke(new Action(() =>
{
Tabs.MoveCurrentTo(CurrentTab);
TabControl.Focus();
}));
}
}
I still have no clue what on Earth is going on here. So I will gladly accept the answer, which sheds some light on this issue.
inside the tabControl_SelectionChanged event handler:
if (e.OriginalSource == tabControl) //if this event fired from your tabControl
{
e.Handled = true;
if (!forbiddenPage.IsSelected) //User leaving the tab
{
if (forbiddenTest())
{
forbiddenPage.IsSelected = true;
MessageBox.Show("you must not leave this page");
}
}
Note that setting forbiddenPage.IsSelected = true causes a loop and you reenter
this event handler. This time, however, we exit because the page selected IS the forbidden page.
private void MainTabControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ReasonBecauseLeaveTabItemIsForbidden)
{
if (MainTabControl.SelectedIndex == IndexOfTabItem)
{
MessageBox.Show(SomeMessageWhyLeaveTabItemIsForbidden);
}
MainTabControl.SelectedIndex = IndexOfTabItem;
}
}
IndexOfTabItem - index of TabItem that disabled for leaving.
He who must be obeyed requested that the application ask the user if they wish to leave the page so here is the slightly changed code:
private Object _selectedTab;
public Object SelectedTab
{
get
{
return _selectedTab;
}
set
{
if (
!(_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel) ||
!_configurationViewModel.HasChanged ||
(System.Windows.Forms.MessageBox.Show("Are you sure you want to leave this page without saving the configuration changes", ADR_Scanner.App.Current.MainWindow.Title, System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Error) == System.Windows.Forms.DialogResult.Yes)
)
{
_selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
I think this small change does pretty much what you wanted.
There is a much easier solution. Add a binding to the selected item in the XAML:
<TabControl SelectedItem="{Binding SelectedTab}" ...
Then in the view model:
private Object _selectedTab;
public Object SelectedTab
{
get
{
return _selectedTab;
}
set
{
if (_selectedTab is ADR_Scanner.ViewModel.ConfigurationViewModel && _configurationViewModel.HasChanged)
{
System.Windows.Forms.MessageBox.Show("Please save the configuration changes", ADR_Scanner.App.ResourceAssembly.GetName().Name, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
else
{
_selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
Obviously you replace ADR_Scanner.ViewModel.ConfigurationViewModel with your own view model class. Lastly make sure you initialise _selectedTab in your constructor otherwise the TabControl will have no initial selection.

Get difference between Window ShowDialog cancel and close

In our app we use WPF and Caliburn Micro. We use a custom WindowManager:
public class OurWindowManager : Caliburn.Micro.WindowManager
{
protected override Window EnsureWindow(object model, object view, bool isDialog)
{
var window = base.EnsureWindow(model, view, isDialog);
if (isDialog) window.ResizeMode = ResizeMode.NoResize;
window.Icon = new BitmapImage(new Uri("pack://application:,,,/NWB.ico"));
// TODO: Change to dynamic minWidth/minHeight based on window
window.MinWidth = 600;
new WindowSettingsBehavior().Attach(window);
return window;
}
}
In our code we mostly use this WindowManager like so:
public void SomeMethod()
{
var result = _windowManager.ShowDialog(new ConfirmDialogViewModel("some title",
"some text"));
if(result == true){ // if OK is pressed
// do something on OK
}
// do nothing
}
In one of my recent methods I want to do the following (in semi pseudo-code):
public void SomeOtherMethod()
{
_windowManager.ShowDialog(new ConfirmDialogViewModel("some title", "some text"));
//if window is closed without pressing any of the buttons
return; // do nothing
//if OK is pressed {
// do something on OK
}
// if Cancel is pressed: do something else
}
Unfortunately, ShowDialog also returns false if the Window is closed (even though the ShowDialog returns a Nullable bool (bool?)).
So, what I did so far is just completely remove the Close Button by making a new Window-Behavior, and I've added it to the OurWindowManager class inside the if(isDialog):
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
}
This works, and I now got a Window with just a title, and without a Close (X) button. Unfortunately, the window can still be closed with Alt+F4 and such. I thought about catching Alt+F4 and cancel the closing, but since Alt+F4 is standard Window behavior, I don't think users will appreciate it a lot and I find it a bit unintuitive for the users to disable it..
So, my question: How can I accomplish the pseudo-code in SomeOtherMethod mentioned above. Is there a way to get the difference between closing a Dialog or canceling a Dialog. (NOTE: As mentioned above, keep in mind we use Caliburn.Micro.WindowManager, not the default C# WPF one. Don't know if there are a lot of differences, but I guess there are at least some.)
EDIT:
I also know I can catch the closing event and cancel the closing:
window.Closing -= DisableDialogClosing;
if (isDialog)
{
window.ResizeMode = ResizeMode.NoResize;
new WindowHideBarBehavior().Attach(window);
window.Closing += DisableDialogClosing;
}
...
private static void DisableDialogClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
}
But then it also cancels the closing when I want it to close (for example when the Cancel/OK button is pressed). Maybe I can add some kind of Property-flag to this overridden Closing-EventHandler, but perhaps you guys/girls have other suggestions to accomplish the same results.
You can fulfil your requirements if you just implement your own dialog Window by extending the Window class. From inside your custom Window, you can handle the Closed event and set the Window.DialogResult property to null in that case. For the normal Ok and Cancel states, you can simply attach Click handlers to those Buttons and set the Window.DialogResult property to true and false accordingly.
private void CustomDialogWindow_Close(object sender, RoutedEventArgs e)
{
DialogResult = null;
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
You could then check for the Window closed state like this:
if (CustomDialogWindow.DialogResult == null) DoSomethingUponDialogWindowClose();
You can find further helpful information in the following pages on MSDN:
Dialog Boxes Overview
Window.DialogResult Property
After #Sinatr's suggestion I've added a ClosedBy property to my ConfirmDialogViewModel:
(before):
public sealed class ConfirmDialogViewModel : Screen
{
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
}
public string Message { get; set; }
public void Ok()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
}
(after):
public sealed class ConfirmDialogViewModel : Screen
{
public ClosedBy CloseReason { get; private set; }
public ConfirmDialogViewModel(string title, string message)
{
DisplayName = title;
Message = message;
CloseReason = ClosedBy.Other;
}
public string Message { get; set; }
public void Ok()
{
CloseReason = ClosedBy.Ok;
TryClose(true);
}
public void Cancel()
{
CloseReason = ClosedBy.Cancel;
TryClose(false);
}
}
public enum ClosedBy
{
Other,
Ok,
Cancel
}
And I now use it like so:
public void SomeOtherMethod()
{
var confirmDialog = new ConfirmDialogViewModel("some title", "some text");
var result = _windowManager.ShowDialog(confirmDialog);
if(result == null || confirmDialog.CloseReason == ClosedBy.Other) return;
if(result == true && confirmDialog.CloseReason == ClosedBy.Ok){
// Do something on OK
}
// Do something on cancel
}
I still kept the behavior to remove the close button, and also added window.ShowInTaskbar = false; to the OurWindowManager inside the if(isDialog).

Get value from dialog window

I'm having trouble getting the value of a textbox of a popped up dialog. I've followed the advice from other StackOverflow questions that said to create a public variable in program.cs:
public static string cashTendered { get; set; }
Then I created my dialog like this:
Cash cashform = new Cash();
cashform.ShowDialog();
And when the user presses the button on the dialog, this is called:
if (isNumeric(textBox1.Text, System.Globalization.NumberStyles.Float))
{
Program.cashTendered = textBox1.Text;
this.Close();
}
else
{
MessageBox.Show("Please enter a valid amount of cash tendered. E.g. '5.50'");
}
Yet Program.cashTendered stays null. Am I doing something wrong? Thanks!
For starters your form called Cash should use an object oriented design. It should have a public property called CashEntered or something similar of type decimal instead of string. You would call the Form like so:
using (var cashDialog = new CashDialog())
{
// pass a reference to the Form or a control in the Form which "owns" this dialog for proper modal display.
if (cashDialog.ShowDialog(this) == DialogResult.OK)
{
ProcessTender(cashDialog.CashEntered);
}
else
{
// user cancelled the process, you probably don't need to do anything here
}
}
Using a static variable to hold the results of a temporary dialog is a bad practice. Here is the better implementation of a dialog:
public class CashDialog : Form
{
public decimal CashEntered { get; private set; }
private void ok_btn_Clicked
{
decimal value;
if (Decimal.TryParse(cashEntered_txt.Text, out value))
{
// add business logic here if you want to validate that the number is nonzero, positive, rounded to the nearest penny, etc.
CashEntered = value;
DialogResult = DialogResult.OK;
}
else
{
MessageBox.Show("Please enter a valid amount of cash tendered. E.g. '5.50'");
}
}
}
On your main form that you want to get the value for, you'd have some sort of code like this;
var cashTendered;
using (var frm = new Cash())
{
if (frm.ShowDialog() == DialogResult.OK)
cashTendered = frm.GetText()
}
Then on your dialog form, you'd have something like this:
public string GetText()
{
return textBox1.Text;
}
public void btnClose_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
public void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
Alternatively, you can just perform those lines in the btnClose_Click event in the FormClosing event instead if you don't have a button for them to click to 'submit' their value.
Edit You might want to add some sort of validation on your textbox inside the btnClose event, such as:
decimal myDecimal;
if (decimal.TryParse(textBox1.Text, out myDecimal))
{
this.DialogResult = DialogResult.OK;
this.Close();
}
else
{
MessageBox.Show("Invalid entry", "Error");
textBox1.SelectAll();
}

DataBinding in winforms : Update even though I click "Cancel"

I have a problem with DataBinding in Winforms, Even though I click "Cancel" on the form, the objecte is updated.
I've set the property "DialogResult" of the Ok button to "OK", of the Cancel button to "Cancel", also, I've set the properties "AccesptButton" and "CancelButton" of the form to bnOk and bnCancel.
Here is my code :
Model :
private string code;
public string Code
{
get { return code; }
set { SetPropertyValue<string>("Code", ref code, value); }
}
private string libelle;
public string Libelle
{
get { return libelle; }
set { SetPropertyValue<string>("Libelle", ref libelle, value); }
}
UI :
public FamilleTiers CurrentFamilleTiers { get; set; }
private void FamilleTiersForm_Load(object sender, EventArgs e)
{
txCode.DataBindings.Add("Text", CurrentFamilleTiers, "Code");
txLibelle.DataBindings.Add("Text", CurrentFamilleTiers, "Libelle");
}
Edit function :
public static void EditFamilleTiers(FamilleTiers selectedFamilleTiers)
{
using (FamilleTiersForm form = new FamilleTiersForm() { CurrentFamilleTiers = selectedFamilleTiers, Text = selectedFamilleTiers.Libelle })
{
if (form.ShowDialog() == DialogResult.OK)
{
form.CurrentFamilleTiers.Save();
}
}
}
Thanks for your time
When you click cancel on a form data binding does not revert you need to keep a backup copy of the values and if they change replace the new values with the original values. .Net does not know what your wanting to do.

My dialog return value doesn't work

I have a custom form that's returning the values to the main form but it's not seeing the variables. I don't think I'm making this very clear so I'll include the links to the examples of what I'm trying to do.
Return values from dialog box
too long to display
I know I'm probably overlooking something very easy and or obvious but here is what I have.
form1.cs:
private void addTime_Click(object sender, EventArgs e)
{
Form add = new addTime(false, new string[] { "", "" });
if (add.ShowDialog(this) == DialogResult.OK)
{
// the line not working
Label1.Text = add.Details;
// reports with:'System.Windows.Forms.Form' does not contain a
// definition for 'Details' and no extension method 'Details' accepting
// a first argument of type 'System.Windows.Forms.Form' could be found (are you
// missing a using directive or an assembly reference?)
}
}
addTime.cs:
internal class addTime : Form
{
//..
private string _details;
public string Details
{
get { return _details; }
private set { _details = value; }
}
private string _goalTime;
public string GoalTime
{
get { return _goalTime; }
private set { _goalTime = value; }
}
private void applybtn_Click(object sender, EventArgs e)
{
Details = detailslbl.Text;
GoalTime = goalTimelbl.Text;
}
}
Your 'add' variable is of type Form, not addTime and the Form type does not have a Details property.
Try this line instead:
addTime add = new addTime(false, new string[] { "", "" });
You need to set the DialogResult property of the child form
DialogResult = DialogResult.OK
in the button click .
you need to set the form's dialogResult property to OK. You haven't specified it in your code.
After the correct criteria have been met you would set it like this.
If (//condition)
{
this.DialogResult = DialogResult.OK;
This.Close();
}

Categories