I want to make a function to work in toggle(switch) mode when i press a key and i really can't figure how to do it. I tried lots of ways and only the "RegisterHotKey" method is working fine. But "RegisterHotKey" is overwriting the mapped key from the game and this is not what i need. So i'm trying to use "GetKeyState". The code below it's only working for one position no matter what i change...:
private void mw_KeyDown(object sender, KeyEventArgs e){
bool sw = (toggle = !toggle);
int tog = (GetKeyState(Key.Tab));
if ((tog & 1) == 1)
{
if (sw)
{
System.Windows.MessageBox.Show("go to second position...!");
}
}
else
{
System.Windows.MessageBox.Show("go to first position...!");
}
}
Any idea or suggestion how can i do this ?
Thank you,
Solution provided by Sergey Alexandrovich Kryukov from CodeProject.
Link: solution
public partial class MainWindow : Window
{
bool toggle;
public MainWindow()
{
InitializeComponent();
MainWindow.KeyDown += (sender, eventArgs) => { if (eventArgs.Key == Key.F7) toggle = !toggle; };
}
private void MainWindow_KeyDown(object sender, EventArgs e)
{
if (toggle)
{
//System.Windows.MessageBox.Show("go to second position...!");
}
else
{
//System.Windows.MessageBox.Show("go to first position...!");
}
}
}
Related
I'm working on a simple UWP app, using Template 10. I want to enter monetary data into a TextBox. It's my understanding that I should use a string variable in the View-Model. So, for the moment I'm just making sure that the data I enter, when running the app, actually works. But it doesn't. When running or debugging it, and if I enter something like "10" (without the double quotes), what the variable value is assigned is "0". Which doesn't make sense to me. Here's the XAML:
<TextBox
x:Name="HourlyTextBox"
Style="{StaticResource CommonTextboxStyle}"
Text="{x:Bind ViewModel.Hourly, Mode=TwoWay}" />
And here's the code from the View-Model:
private string hourly;
public string Hourly
{
get => hourly;
set
{
_ = Set(ref hourly, value);
}
}
Here's the Code-behind code:
using Windows.UI.Xaml.Controls;
using SalaryConv;
namespace SalaryConversion.Views
{
public sealed partial class MainPage : Page
{
private SalaryUnitsEnum lastHadFocus;
public MainPage()
{
InitializeComponent();
NavigationCacheMode =
Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
}
#region GettingFocus events
private void HourlyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.Hourly)
{
return;
}
lastHadFocus = SalaryUnitsEnum.Hourly;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
private void WeeklyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.Weekly)
{
return;
}
lastHadFocus = SalaryUnitsEnum.Weekly;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
private void BiWeeklyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.BiWeekly)
{
return;
}
lastHadFocus = SalaryUnitsEnum.BiWeekly;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
private void SemiMonthlyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.SemiMonthly)
{
return;
}
lastHadFocus = SalaryUnitsEnum.SemiMonthly;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
private void MonthlyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.Monthly)
{
return;
}
lastHadFocus = SalaryUnitsEnum.Monthly;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
private void AnnuallyTextBox_GettingFocus(Windows.UI.Xaml.UIElement sender, Windows.UI.Xaml.Input.GettingFocusEventArgs args)
{
if (lastHadFocus == SalaryUnitsEnum.Annually)
{
return;
}
lastHadFocus = SalaryUnitsEnum.Annually;
ClearOtherMonetaryTextboxes(lastHadFocus);
}
#endregion
#region ClearOtherMonetaryTextboxes helper method
private void ClearOtherMonetaryTextboxes(SalaryUnitsEnum lastHadFocus)
{
if (lastHadFocus != SalaryUnitsEnum.Hourly)
{
HourlyTextBox.Text = "0";
}
if (lastHadFocus != SalaryUnitsEnum.Weekly)
{
WeeklyTextBox.Text = "0";
}
if (lastHadFocus != SalaryUnitsEnum.BiWeekly)
{
BiWeeklyTextBox.Text = "0";
}
if (lastHadFocus != SalaryUnitsEnum.SemiMonthly)
{
SemiMonthlyTextBox.Text = "0";
}
if (lastHadFocus != SalaryUnitsEnum.Monthly)
{
MonthlyTextBox.Text = "0";
}
if (lastHadFocus != SalaryUnitsEnum.Annually)
{
AnnuallyTextBox.Text = "0";
}
}
#endregion
}
}
Thanks to Richard Zhang's suggestion of looking at my code-behind, I discovered there that I had previously written some code to handle the controls on the screen. It was this code which was resetting the values to 0 (indirectly). I had written that code a while ago, so I'd forgotten all about it.
Thank you, Richard, for making that suggestion. It helped me see what I had done and after reviewing it I was able to easily resolve it.
I have an application that has a form named as MainAppForm (Thread1). I have a panel in this form which will host UserControls.
When a user clicks the button, I want to create another thread (Thread2) which will create an instance of the UserControl and call a method that is on the Thread1 to add UserControl to the panel in mentioned in the first paragraph.
This is how I call main Thread1 from Thread2
public class SecondThread
{
public void start()
{
ModuleWindow userControl = new ModuleWindow(new Module.ModuleLayer());
Global.SetModuleWindowThreadSafe(userControl);
}
}
My method that will add the passed in user control to the panel.
public static class Global
{
private delegate void SetModuleWindowThreadSafeDelegate(UserControl userControl);
public static void SetModuleWindowThreadSafe(UserControl userControl)
{
if (Global.mainAppForm.pnlMain.InvokeRequired)
{
Global.mainAppForm.pnlMain.Invoke(
new SetModuleWindowThreadSafeDelegate(SetModuleWindowThreadSafe),
userControl);
}
else
{
Global.mainAppForm.pnlMain.Controls.Add(userControl);
}
}
}
After I do the call in the SetModuleWindowThreadSafe() method it raises
Cross-thread operation not valid: Control 'menuStrip1' accessed from a thread other than the thread it was created on.
Note: menuStrip1 is a control on UserControl.
How can I add the UserControl that is created in the second thread to the panel???
UPDATED:
Thanks for the answers. I am sure they're helpful in some ways but not in my condition. The reason is my MainAppForm(AKTAP project) and the generated UserControl's(KKM project) are being created in different projets even solutions. The project output of KKM is a .dll and I am loading those dll files on runtime using reflections. So MainAppForm does not know what type of usercontrols and controls are being generated in each dll.
What I want to do is in the following order:
1- AKTAP project has an interface which is implemented by a class in KKM project.
2- KKM project is being built and puts dll files to a specified directory.
3- AKTAP starts to run and loads dll files using reflections by filtering the interface mentioned in 1.
4- AKTAP calls a method in KKM hich will generate and return the usercontrol.
5- AKTAP adds the returned usercontrol to the MainAppForm. (And this is where I get the exception above.)
How can I add the UserControl that is created in the second thread to the panel?
You don't. You create the UserControl in the UI thread, rather than in some background thread.
If you have some expensive CPU bound computation to do in order to figure out what data the user control will need then use another thread to compute that data and then have the UI thread take that data and create the UI controls to display it.
Servy is correct - you don't.
However, you can! Meaning, it is possible.
Passing data from a thread is complicated, but the System.ComponentModel.BackgroundWorker (part of WinForms) greatly simplifies threading operations and makes tasks like this rather fun to do.
Here is a generic technique that uses two (2) Windows Forms, one as a variable inside the other. Both are in the same namespace (same project, etc).
Form1:
public partial class Form1 : Form
{
private Button btnGetInteger;
private Button btnGetMenuStrip;
private Button btnGetString;
private Form2 _form2;
private Form2.ReturnType _getType;
private Object _form2Argument;
public Form1()
{
InitializeComponent();
btnGetInteger = new Button();
btnGetInteger.Click += Form2_GetInteger;
btnGetMenuStrip = new Button();
btnGetMenuStrip.Click += Form2_GetInteger;
btnGetString = new Button();
btnGetString.Click += Form2_GetString;
Shown += (s, e) => { Form2_CreateMenuStrip(s, EventArgs.Empty); };
}
public void Form2_ThreadChanged(object sender, ProgressChangedEventArgs e)
{
var returned = (Form2.ReturnType)e.ProgressPercentage;
switch (returned)
{
case Form2.ReturnType.MenuStrip:
var menuStrip = (MenuStrip)e.UserState;
this.Controls.Add(menuStrip);
break;
case Form2.ReturnType.Integer:
var numberBack = (int)e.UserState;
Text = String.Format("Form1 : (int){0}", numberBack);
break;
case Form2.ReturnType.String:
var stringBack = e.UserState.ToString();
Text = String.Format("Form1 : (String){0}", stringBack);
break;
}
}
public void Form2_ThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_form2Argument = null;
if (e.Error != null)
{
String title;
if (_form2 != null)
{
title = String.Format("{0}: {1}", _form2.Text, e.Error.GetType());
} else
{
title = String.Format("Form2: {0}", e.Error.GetType());
}
MessageBox.Show(e.Error.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (_form2 != null)
{
_form2.Close();
_form2.Dispose();
_form2 = null;
}
btnGetInteger.Enabled = true;
btnGetMenuStrip.Enabled = true;
btnGetString.Enabled = true;
}
private void Form2_CreateMenuStrip(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.MenuStrip;
var item = new ToolStripMenuItem(Text);
item.Click += Form2_GetInteger;
_form2Argument = item;
Form2_StartWork();
}
}
private void Form2_GetInteger(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.Integer;
Form2_StartWork();
}
}
private void Form2_GetString(object sender, EventArgs e)
{
if (_form2 == null)
{
_getType = Form2.ReturnType.String;
Form2_StartWork();
}
}
private void Form2_StartWork()
{
btnGetInteger.Enabled = false;
btnGetMenuStrip.Enabled = false;
btnGetString.Enabled = false;
_form2 = new Form2();
_form2.Show(); // Show returns immediately
_form2.StartThread(this, _form2Argument, _getType);
}
}
Form2_ThreadChanged and Form2_ThreadCompleted are both set to PUBLIC so that they can be visible by the instance of Form2.
Form2:
public partial class Form2 : Form
{
private ReturnType _getType; // thread safe
private BackgroundWorker _bwThread;
public enum ReturnType { MenuStrip, String, Integer }
public Form2() // Do Not Call this method
{
InitializeComponent();
}
public void StartThread(Form1 parent, Object argument, ReturnType getType)
{
_getType = getType;
if (_bwThread == null)
{
_bwThread = new BackgroundWorker() {
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_bwThread.DoWork += ThreadWork;
_bwThread.ProgressChanged += parent.Form2_ThreadChanged;
_bwThread.RunWorkerCompleted += parent.Form2_ThreadCompleted;
}
if (!_bwThread.IsBusy)
{
_bwThread.RunWorkerAsync(argument);
}
}
private void ThreadWork(object sender, DoWorkEventArgs e)
{
switch (_getType)
{
case ReturnType.MenuStrip:
var menuStrip = new MenuStrip();
if (e.Argument != null)
{
var mi = (ToolStripMenuItem)e.Argument;
menuStrip.Items.Add(mi);
}
_bwThread.ReportProgress((int)_getType, menuStrip);
break;
case ReturnType.Integer:
var numberBack = 1;
_bwThread.ReportProgress((int)_getType, numberBack);
break;
case ReturnType.String:
var stringBack = "Worker String";
_bwThread.ReportProgress((int)_getType, stringBack);
break;
}
}
}
If you make a new, small project with 2 empty forms in it called Form1 and Form2, you can go into the code and simply paste everything from above into those two forms.
With that done, just put breakpoints on all of the methods (both public and private) to see how they work.
I have separated two projects in my solution because they each require libraries targeting different CPU.
In one of my project, I just have classes that respond to clicks (let's call it ProjectClick 64 bits libraries), the other one is a sort of UI with an MVVM implementation (ProjectUser 32 bits libraries).
The thing I am searching for is a way to let the ProjectUser know that the click has been performed by the ProjectClick, without the Project Click knowing anything else.
What I have tried so far
I have been scattering the web and books to understand a bit more about C#. From what I understood, to communicate, the best way is to create a Interface. I have been looking at this subject for an answer, and have been trying to implement a third project with an interface between the two.
Ok, here goes the code, (this is a purposely simplified code, I hope it is clear enough)
First the Interface (in a console application)
namespace LinkApplication
{
public interface IEvent
{
bool CompareClick { get; set; }
}
}
Then, the project clicking which is a wpf
namespace ProjectClick
public partial class MainWindow : Window, IEvent
{
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CompareClick = true;
}
private void Button_Leave(object sender, RoutedEventArgs e)
{
CompareClick = false;
}
}
Finally the UI
namespace ProjectUser
{
public partial class MainWindow : Window, IEvent, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
}
public bool CompareClick { get; set; }
public bool ClickCheck
{
get { return CompareClick; }
set
{
if (value != CompareClick)
{
CompareClick = value;
OnPropertyChanged("ClickCheck");
}
}
}
You can see the realted Label here in the Window
<Label Content="{Binding ClickCheck}" HorizontalAlignment="Left" Margin="690,358,0,0" VerticalAlignment="Top"/>
Here, the value always stays at false, and I don't really understand the logic of the changing value. I am still learning, and I have seen several other ideas on the web like a custom EventHandler, but I don't really understand the implementation between two projects not knowing each others. I will be glad if someone could route me towards a possible solution, or a better way to perform.
Edit
I would preferably like to avoid referring the Project Click in the ProjectUser to keep the privileges of different CPU targeting. The other way around is not a problem.
Thank you for your kind answers.
I have been greatly advised and have looked into Inter Process Communication between instances. I have looked into different things but the most satisfying answer of all was on Omegaman's blog (bit thanks to this subject).
So basically, I have tried to avoid localhost information, thinking there would be a more straightforward solution. But since we have not thought of anything better, I think this is what I was looking for.
What I have found
So now, the solution here was to use a WCF service with NamedPipes. By creating a Sender and Receiver actions, the two process ProjectUser and ProjectClick never encounter each other directly. You have instead a pipe controlled by the WCF. You can see more details on the blog on how to communicate, I just adapted (without great change) what he did by changing the passing information.
One thing to note however
The processes cannot both start at the same time, and the receiver must start first to listen to the information coming through. Basically, the sender has to start afterwards.
I created two windows in WPF, and a WCFServiceLibrary. When the button is clicked, there is an incrementation, and it shows the number on the second screen.
A little bit of code
You can see a lot on Omegaman's blog, and I will just post what I have changed.
On the ProjectUser side, supposed to receive, the label is updated as follows
Receiver pipe = new Receiver();
public MainWindow()
{
InitializeComponent();
//this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
pipe.Data += new PipeLink.PipeService.DataIsReady(DataBeingRecieved);
if (pipe.ServiceOn() == false)
MessageBox.Show(pipe.error.Message);
label1.Content = "Listening to Pipe: " + pipe.CurrentPipeName + Environment.NewLine;
}
void DataBeingRecieved(int data)
{
Dispatcher.Invoke(new Action(delegate()
{
label1.Content += string.Join(Environment.NewLine, data);
label1.Content += Environment.NewLine;
}));
}
On the ProjectClick side, supposed to send, the button click updates as follows
int i;
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
i = 0;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
int messages;
i++;
Stopwatch stoop = new Stopwatch();
stoop.Start();
messages = i;
try
{
PipeLink.Sender.SendMessage(messages);
stoop.Stop();
Console.WriteLine(stoop.ElapsedMilliseconds + " ms");
}
catch (Exception u)
{
Console.WriteLine(u);
}
}
The important part of the code, is the creation of the pipe itself, using NetNamedPipeBinding. This is where the whole communication will take place
You can see it in the PipeService code :
public class PipeService : IPipeService
{
public static string URI
= "net.pipe://localhost/Pipe";
// This is when we used the HTTP bindings.
// = "http://localhost:8000/Pipe";
#region IPipeService Members
public void PipeIn(int data)
{
if (DataReady != null)
DataReady(data);
}
public delegate void DataIsReady(int hotData);
public DataIsReady DataReady = null;
#endregion
}
What about the speed?
I was afraid simple data may take longer to arrive than on a simple click. I was mistaken : the first number took longer than the others because of the first connection, so about a second. But after that, for clicking about a 100 times, I had a, average of 10 ms (I know it is not significant data, still I thought it was good to test it a couple of times).
I am pushing everything on the GitHub used with Andreas, for anyone who might be interested.
I still do not know if the code is optimized though. Should you have a better solution, I will happily read it.
As others pointed out your concept of interfaces is wrong still. However i get what you're trying to do.
Try this:
namespace LinkApplication
{
public interface IEventReceiver
{
void Receive<T>(T arg) where T : EventArgs;
}
public class SomeUniqueEvent : EventArgs
{
public bool Clicked { get; set; }
public SomeUniqueEvent(bool clicked)
{
Clicked = clicked;
}
}
public static class EventTunnel
{
private static readonly List<IEventReceiver> _receivers = new List<IEventReceiver>();
public static void Publish<T>(T arg) where T : EventArgs
{
foreach (var receiver in _receivers)
{
receiver.Receive(arg);
}
}
public static void Subscribe(IEventReceiver subscriber)
{
_receivers.Add(subscriber);
}
}
}
namespace ProjectClick
{
public partial class MainWindow : Window
{
public MainWindow()
{
try { InitializeComponent(); }
catch (Exception e)
{
Console.WriteLine(e.InnerException);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(true));
}
private void Button_Leave(object sender, RoutedEventArgs e)
{
LinkApplication.EventTunnel.Publish(new LinkApplication.SomeUniqueEvent(false));
}
}
}
namespace ProjectUser
{
public partial class MainWindow : Window, LinkApplication.IEventReceiver, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //start the window at the centre of the screen
DataContext = this;
LinkApplication.EventTunnel.Subscribe(this);
}
public bool CompareClick { get; set; }
public bool ClickCheck
{
get { return CompareClick; }
set
{
if (value != CompareClick)
{
CompareClick = value;
OnPropertyChanged("ClickCheck");
}
}
}
public void Receive<T>(T arg) where T : EventArgs
{
var casted = arg as SomeUniqueEvent;
if (casted != null)
{
ClickCheck = casted.Clicked;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here, you misunderstand what an interface is. Every implementation of an interface is a different one. When you click the button, CompareClick property of ProjectClick project's MainWindow changes value. But that doesn't change the ProjectUser project's MainWindow. They are two completely different objects! The best way that I can think of now, is to make the button public. Alternatively, you can create a method in the MainWindow class of the ProjectClick. Use this method to subscribe to the click event. Something like this:
public void SubscribeToClickEvent (EventHandler handler) {
this.Button.Click += handler //whatever your button is called
}
If you want to encapsulate Button, use the method above. If you don't, then just make it public.
And you ask, how can I access an instance of MainWindow to use the method? The only way I can think of is to make MainWindow a singleton.
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.
Okay, I have a form called settings that I'm using to set hot key's and other stuff through out my form but the issue is once set you need to restart the app for them to take affect, my code is this:
In main class:
public void setHotkeys()
{
if (Properties.Settings.Default.F4 == true)
{
Pvt.HookedKeys.Add(Keys.F4);
MessageBox.Show("checked F4");
}
if (Properties.Settings.Default.F5 == true)
{
Pvt.HookedKeys.Add(Keys.F5);
MessageBox.Show("checked F5");
}
Pvt.KeyDown += new KeyEventHandler(ghk_startPvt);
Group.KeyDown += new KeyEventHandler(ghk_startGroup);
}
Settings form class for saving / setting the settings:
private void settingsPwn4g3_Load(object sender, EventArgs e)
{
settingsLoad();
}
public void saveFields()
{
Properties.Settings.Default.autoAttach = Autoattach.Checked;
Properties.Settings.Default.F5 = F5.Checked;
Properties.Settings.Default.F4 = F4.Checked;
Properties.Settings.Default.Save();
}
public void settingsLoad()
{
Autoattach.Checked = Properties.Settings.Default.autoAttach;
F5.Checked = Properties.Settings.Default.F5;
F4.Checked = Properties.Settings.Default.F4;
}
private void button1_Click(object sender, EventArgs e)
{
pwn4g3 Pwn4g3 = new pwn4g3();
saveFields();
Pwn4g3.setHotkeys();
}
Not to mention this way of doing it is very annoying for over 200 settings this can take days. Any other ideas? I would like to use a variable for set hotkey's then have something where they can press the hot key and set it, rather then 200 check box's for every key on the keyboard.