How to avoid multiple event fire when WPF page is opened again? - c#

I have a frame and few pages in my WPF application.
My navigation is controlled by buttons. On each button I have click handler that creates new page with some parameters:
private void ButtonProductionAuto_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionAuto(someobject, this);
}
private void ButtonProductionManual_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionManual(someobject, this);
}
When I'm switching between pages - previous pages still exist in memory and they react on some custom events.
(edit)
This is my code related with events:
public PageProductionAuto(ref TechModbus modbus, MainWindow wnd)
{
// ...
wnd.KeyDown += Wnd_KeyDown;
wnd.KeyUp += Wnd_KeyUp;
m.OnReadFinished += Modbus_OnReadFinished;
// ...
}
How can I dispose these pages or how can I avoid double-fire on my events when page is opened second time?

You should unregister the events on leaving the page.
GarbageCollector will then "dispose" (it's actually not a dispose) by itsself when there are no more references on those objects(PageProductionAuto and PageProductionManual).
Quoting MS:
The reason WPF controls don't implement IDisposable is because they have nothing to dispose. They have no handle to clean up, and no unmanaged resources to release. To ensure your memory is cleaned up, just make sure nothing has a reference to the controls once you're finished with them.

Your question is incomplete. But I can answer the question about "how to avoid multiple instances" part of it. To dispose your pages, you have to detach your events, remove them from the "openedPages" collection, and dispose where possible.
List<object> openedPages = new List<object>();
private void ButtonProductionAuto_OnClick(object sender, RoutedEventArgs e)
{
var page = openedPages.FirstOrDefault(p => p.GetType().Equals(typeof(PageProductionAuto)));
if(page == null)
{
page = new PageProductionAuto(someobject, this);
opendPages.Add(page);
}
else
{
page.SetObjects(someobject, this); // create a method to set "someObject" to your page.
}
FrameMain.Content = page;
}

What I did to avoid this was I have a window with 2 frames in it. I placed one of each source XAML in each frame so frame 1 was XAML 1 and frame 2 was XAML 2. Then I just edited the visibility to collapsed and visible. Then you don't have any page changes or instances getting created. You just create the original 2 instances.

Related

MS Band SDK - Button pressed event handler not being called

I've set up my tile and page layout with a button in my app but when I press the button the event handler does not get called. I tried with the tile open event handler but that doesn't work either. My code is as follows:
private async void OnConnectToBand()
{
IBandInfo[] pairedBands = await BandClientManager.Instance.GetBandsAsync();
try
{
using (IBandClient bandClient = await BandClientManager.Instance.ConnectAsync(pairedBands[0]))
{
//add tile, create page layout with button and add content with button
//subscribe to listeners
bandClient.TileManager.TileButtonPressed += EventHandler_TileButtonPressed;
// Start listening for events
bandClient.TileManager.StartReadingsAsync();
}
}
catch(BandException ex)
{
//handle a Band connection exception
}
}
void EventHandler_TileButtonPressed(object sender, BandTileEventArgs<IBandTileButtonPressedEvent> e)
{
// handle event
}
The tile and page get created fine but the button doesn't trigger the event handler. Any ideas why it's not being called?
UPDATE: I just went through my code and the SDK doco again and remembered I'm doing something different which is why it might not be working. The doco has the following for adding the button to the layout which doesn't compile:
// create the content to assign to the page
PageData pageContent = new PageData
(
pageGuid,
0, // index of our (only) layout
new Button(
TilePageElementId.Button_PushMe,
“Push Me!”)
);
The compiler says there isn't a constructor for Button that takes in 2 arguments.
I assumed there was an error in the sample code and changed it to TextButtonData which compiles fine but now I'm wondering if that is why the event handler isn't working? Code is:
PageData pageContent = new PageData(
pageGuid,
0, // index of our (only) layout
new TextButtonData(
(short)TilePageElementId.Button_PushMe, "Push"));
Any ideas?
This is great to see someone developing on the MS Band.... heres a few links that discuss the OnConnectToBand and its setup
void EventHandler_TileButtonPressed(object sender,
BandTileEventArgs<IBandTileButtonPressedEvent> e)
{
// This method is called when the user presses the
// button in our tile’s layout.
//
// e.TileEvent.TileId is the tile’s Guid.
// e.TileEvent.Timestamp is the DateTimeOffset of the event.
// e.TileEvent.PageId is the Guid of our page with the button.
// e.TileEvent.ElementId is the value assigned to the button
// in our layout (i.e.,
// TilePageElementId.Button_PushMe).
//
// handle the event
}
Section 9- Handling custom events
http://developer.microsoftband.com/Content/docs/Microsoft%20Band%20SDK.pdf
Talks about adding, clicking, removing tiles
http://www.jayway.com/2015/03/04/first-impression-of-microsoft-band-developing-2/
Try adding a dialog(below is windows code, for ios or android have a look at the above mentioned manual) to respond to the event (in your code above there is nothing in your event handler? this to see if it actually does something?
using Microsoft.Band.Notifications;
try
{
// send a dialog to the Band for one of our tiles
await bandClient.NotificationManager.ShowDialogAsync(tileGuid,
"Dialog title", "Dialog body");
}
catch (BandException ex)
{
// handle a Band connection exception
}
You can only receive events from the Band while you have an active IBandClient instance (i.e. an active connection to the Band). In your code above, the bandClient instance is disposed of immediately after StartReadingsAsync() is called, due to the use of the using() {} block. When an IBandClient instance is disposed, it causes the application to disconnect from the Band.
You need to hold onto the IBandClient instance for the length of time during which you wish to receive events, and dispose of the instance only after that time.

Update 2 different classes from a settings flyout?

As per MSDN guidelines we need to put all the app's settings into the SettingsPane and then the app should update all pages when the settings is applied.
In my app I need to have a reset option which brings the app to the default settings. There are 2 pages, Calendar.xaml and HistoryStatistics.xaml that i need to update when the reset button is pressed. All the data of the app is put in a singleton class called CycleManager. I have used a SettingsFlyout control from the Callisto Toolkit.
App.Xaml
Registered the settings in the App.xaml
SettingsPane.GetForCurrentView().CommandsRequested += OnCommandsRequested;
and in OnCommandsRequested function, created the reset handler
var reset = new SettingsCommand("reset", "Reset", (handler) =>
{
var settings = new SettingsFlyout();
settings.Content = new ResetUserControl();
settings.HeaderBrush = new SolidColorBrush(_background);
settings.Background = new SolidColorBrush(_background);
settings.HeaderText = "Reset";
settings.IsOpen = true;
});
args.Request.ApplicationCommands.Add(reset);
CycleManager.cs
In the CycleManager class, there is a m_Reset variable,its setter and getter and an event handler called ResetClicked
public event EventHandler ResetClicked;
public bool Reset
{
get
{
return m_reset;
}
set
{
m_reset = value;
if (ResetClicked != null)
ResetClicked(this, EventArgs.Empty);
}
}
Next is the part where i have associated this handler in my first class calendar.xaml
Calendar.xaml
In the constructor of the class I declare the event handler
CycleManager pCycMan = CycleManager.Instance;
pCycMan.ResetClicked += this.ResetClicked;
followed by the definition of the event handler
private async void ResetClicked(object sender, EventArgs e)
{
CycleManager pCycMan = CycleManager.Instance;
if (pCycMan.Reset == true)
{
try
{
await Windows.Storage.ApplicationData.Current.ClearAsync(Windows.Storage.ApplicationDataLocality.Local);
pCycMan.InitializeValues();
}
catch (Exception)
{
}
}
CreateCalendar();// UI is loaded
}
In the constructor of the HistoryStatistics.xaml I have done the same thing as above
HistoryStatistics.xaml
public HistoryStatistics()
{
CycleManager pCycMan = CycleManager.Instance;
pCycMan.ResetClicked += this.ResetClicked;
}
and defined
private void ResetClicked(object sender, EventArgs e)
{
CycleManager pCycMan = CycleManager.Instance;
if (pCycMan.Reset == true)
{
await Windows.Storage.ApplicationData.Current.ClearAsync(Windows.Storage.ApplicationDataLocality.Local);
pCycMan.InitializeValues();
LoadListView();// loads the UI
DisplayStatistics();//loads the UI for the page
}
}
Now the problem
Is this the right approach?
When Reset is pressed in the first from the second page(HistoryStatistcs), the reset clicked function declared in the first page(Calendar.xaml.cs) is called first and then the one in HistoryStatistics. And both gets executed async! :(
Is this a right behaviour?
This question is quite long. Hope everybody understood the scenario and question.
There is nothing wrong with the behaviour you outlined. Two pages subscribe to an event and event uses multi cast delegate which means they will both get fired.
I think you need a simpler behaviour here. Each xaml page should subscribe to that event on OnNavigatedTo and should unsubscribe in OnNavigatedFrom.
That way only one of the two actually executes the cleanup.
The complexity/confusion is likely coming because of not using the MVVM (model, view, and view model) separation. you may want to read about this. keeping the separation helps. Below are few pointers on this. but not necessarily a full design for your app.
in this example: CycleManager.Instance is kind of serving the model (the data). You may want to rename ResetClicked to SettingChanged and think of the event as notification for clients that one or more settings properties exposed has changed. It should also expose ResetSettings() method that can be called by ResetUserControl.
// model for the settings
class SettingsManager
{
public event EventHandler SettingsChanged;
public async void ResetSettings()
{
await Windows.Storage.ApplicationData.Current.ClearAsync
(Windows.Storage.ApplicationDataLocality.Local);
// initialize all values to default values;
this._intializeValues();
if (this.SettingsChanged != null)
this.SettingsChanged(this, EventArgs.Empty);
}
}
HistoryStatistics and Calendar class should have view model that should listen for SettingsChanged event and update the properties exposed. Each page view (xaml) binds to the properties exposed by the respective view model. This will require some refactoring of current code.
Without that, ResetClick eventhandlers can be changed to SettingChanged event handlers and take required action. They need not call setting mgr to initialize values.
class HistoryStatistics
{
private void SettingsChanged(object sender, EventArgs e)
{
SettingsManager settingsManager = SettingsManager.Instance;
LoadListView();// loads the UI
DisplayStatistics();//loads the UI for the page
}
}
HTH.

Managing EventHandlers within a large Form

I'm developing a WinForm application and I've done a pretty bad job thus far of managing the size and contents. I was hoping someone could give me an example of how to break out some of the logic that I have within the main form cs file.
Here is an example of an EventHandler function that I have within my MainWindow.cs:
private void GroupBoxRequestTypeCheckedChanged(object pSender, EventArgs pEventArgs)
{
RadioButton vRadioButton = pSender as RadioButton;
if (vRadioButton != null)
{
this.fSelectedButton = vRadioButton.Checked ? vRadioButton : null;
if (vRadioButton.Equals(this.RadioButton_Copy) || vRadioButton.Equals(this.RadioButton_Delete) || vRadioButton.Equals(this.RadioButton_Download)
|| vRadioButton.Equals(this.RadioButton_Move) || vRadioButton.Equals(this.RadioButton_Upload))
{
this.GroupBox_Files.Enabled = true;
this.GroupBox_Variables.Enabled = false;
}
else
{
this.GroupBox_Files.Enabled = false;
this.GroupBox_Variables.Enabled = true;
}
if (this.fSelectedButton != null)
{
if (this.fSelectedButton.Equals(this.RadioButton_Delete))
{
this.TextBox_DestinationFile.Enabled = false;
this.Button_DestinationBrowse.Enabled = false;
}
else
{
this.TextBox_DestinationFile.Enabled = true;
this.Button_DestinationBrowse.Enabled = true;
}
}
}
}
So this is simply one of many EventHandler's that I have within a Form. I created a MainForm which has a Tabbed Pane and has a collection of Tabs which have buttons, textboxes, checkboxes etc in each tab. All of the events that I handle go into the MainForm.cs file and now I've got close to 1,000 lines in this one file.
Can someone give me a simple example (or a article/document) detailing good structure? Can I define my EventHandler functions in a separate class (if so, how would this work...) Do I create some sort of static Helper class where I simply pass the instance of the objects i need to manipulate? I.E.
private void GroupBoxRequestTypeCheckedChange(object pSender, EventArgs pEventArgs)
{
HelperClass.HandleGroupBoxRequestTypeCheckedChanged(pSender, pEventArgs, this);
}
Where 'this' is the Form itself which has all the references to the objects I need to manipulate?
It's probably worth noting that I've learned a good bit about the Cross-Thread calls and I've started making Extension methods for many instances that I need which are simplistic.
Another question - I notice that the Visual Designer automatically makes all Components created with it private by default, is it in general a bad idea to make these internal and use the form object to reference these components as needed from outside the class? If it is not a good idea, what is better?
First I would suggest to separate independent user-interface parts into UserControls or Components. Then - if needed - wire them using Events (eg. your own specialized events and properties.
For example you can place your main content (the TabControl / Container) in a UserControl and place that user control in the main form. All tab-/page-switching logic/UI etc. then belongs to that user control. In that UserControl you can define for example your own Event that gets fired when the user switches a tab. The main form then can register to this event - just like it can for other Winforms-control-events - and do its stuff (eg. change the window title to represent the currently active tab).
Then next you can move the content of each tab to its own user-control and use these user-controls within your new tabs-usercontrol. Move the logic down to the UserControl which is responsible for the given task.
A form/controls hierarchy from some typical application could look like this:
MainForm (Form)
MainTabContainerControl (UserControl)
Page1Control (UserControl)
Page2Control (UserControl)
MyImprovedDbRowGridControl (UserControl or Component)
Page3Control (UserControl)
SidebarControl (UserControl)
SearchControl (UserControl)
MyImprovedDbRowGridControl (UserControl or Component)
QuickHelpControl (UserControl)
Next thing is so keep all the UI-eventhandlers as small as possible and doing only UI stuff. Move other logic like business- or dataaccess-logic to other classes outside of the user-interface.
If you have combinations of the controls that are needed more then once in the application: move them to a re-usable UserControl. (eg. breadcrum).
Regarding your sample code you can make it more compact and therefore maintainable by simplyfing its logic:
if (this.fSelectedButton.Equals(this.RadioButton_Delete))
{
this.TextBox_DestinationFile.Enabled = false;
this.Button_DestinationBrowse.Enabled = false;
}
else
{
this.TextBox_DestinationFile.Enabled = true;
this.Button_DestinationBrowse.Enabled = true;
}
...could be:
var delete = fSelectedButton == RadioButton_Delete;
this.TextBox_DestinationFile.Enabled = !delete;
this.Button_DestinationBrowse.Enabled = !delete;
Update:
When it comes to refactoring and code-cleanup a very usefull tool is Resharper (R#). I can highly recommend it.
Hope this gives you some ideas where to start.

Cancel all events from Page_Load

EDIT: for those who come here with a similar problem, now i know this was a BAD IDEA.
hi,
I have something like this:
bool preventEvents;
protected void Page_Load(object sender, eventargs e)
{
preventEvents = doSomeValidation();
}
protected void Button1_Click(object sender, EventArgs e)
{
if (preventEvents) return;
// ...
}
protected void Repeater1_DataBound(object sender, EventArgs e)
{
if (preventEvents) return;
// ...
}
The problem is that I have A LOT of events on the page.
Is it possible to just cancel all further events without adding the "if" line on every method?
EDIT:
got some interesting answers (thanks to everyone) but not what i was looking for, maybe i should be more specific:
given some condition, is it possible to skip all events after Page_Load and just jump to the rendering, without manually removing/mapping each event?
The problem is that I have A LOT of events on the page.
Yes, that is a problem. Many events in the same page are bad for performance (it means you're storing a lot of state and doing many http requests). They are bad for maintainability (you have a lot of code in the same class that's all jumbled together). They are bad for testability (asp.net events are notoriously hard to unit test). And they are bad for usability (can't bookmark, don't work with the back button, can lead to double posts).
The solution is to use the Post/Redirect/Get pattern. The downside is that it will mean re-thinking parts of your application design, but in the end you'll have an app that just works better and faster and is easier to maintain.
Be careful choosing to just skip event processing, as is your plan. Odds are your current page state is the result of several events, and not processing events can break the expected state of your page.
You can't "jump ahead" to the rendering because there aren't any conditionals in ProcessRequestMain to allow for it. The only option is to hack the event handlers of the relevant controls.
protected void Page_Load(object sender, EventArgs e) {
// DON'T DO THIS!! Years from now, some poor soul tasked with
// debugging your code will tear their hair out, until they
// discover the unholy magic you have conjured herein. Keep in mind
// this is the 21st century and this person knows where you live.
//
// Seriously, just use the validation built in to ASP.NET.
if ("true".Equals(Request.QueryString["disable_events"], StringComparison.OrdinalIgnoreCase)) {
// disable *all* event handlers on button controls
foreach (var b in this.GetControlDescendants().OfType<Button>()) {
var eventList = (EventHandlerList) typeof (Control)
.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(b, null);
typeof (EventHandlerList)
.GetField("head", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(eventList, null);
}
}
}
Utility extension method for completeness:
/// <summary>
/// Performs a breadth-first traversal of a control's control tree. Unlike
/// FindControl, this method does not descend into controls that have not
/// called EnsureChildControls yet.
/// </summary>
/// <returns>Enumerable of the visited controls.</returns>
public static IEnumerable<Control> GetControlDescendants(this Control parent) {
// Don't force execution of EnsureChildControls
if (!parent.HasControls()) yield break;
foreach (Control child in parent.Controls) {
yield return child;
}
foreach (Control child in parent.Controls) {
foreach (var descendant in child.GetControlDescendants()) {
yield return descendant;
}
}
}
You can use the following but it will be almost the same cost
Protected void Page_Load() {
if (preventEvents) {
textbox1.TextChanged -= textbox1_TextChanged;
dropdownlist1.SelectedIndexChanged -= dropdownlist1_SelectedIndexChanged;
// and so on
}
}
You can create wrapping delegate over event handler, something like this:
private EventHandler CreateCancelableEventHandler(EventHandler handler)
{
return (sender, e) =>
{
if (!preventEvents)
{
handler.Invoke(sender, e);
}
};
}
The disadvantage of this solution is that you will need to subscribe to all events in code-behind, not in the markup. Usage of the subscribing would be like this:
button1.OnClick += CreateCancelableEventHandler(Button1_OnClick);
How about setting AutoEventWireup to False in your page directive? ie.
<%# Page Language="C#" AutoEventWireup="false" Inherits="MyWebApp.EventWireUpFalse" %>
That way only events you explicitly "wire-up" in OnInit will be called. This will give you far more control of what events are raised in the page life-cycle.
Use a custom validator, and then it just falls into your standard invalid check.
I am having the same problem. My current approach is to overwrite the RaisePostBackEvent Method and check a "cancelEvents" flag. RaisePostBackEvent is responsible to route the postback to its originator.
I'm still testing it for bad side effects - I'd appreciate a note if anybody has experience with this idea.

Does data binding work on invisible control?

This is a .net problem with winforms, not asp.net.
I have a windows form with several tabs. I set data bindings of all controls when the form is loaded. But I have noticed that the data bindings of controls on the second tab do not work. Those bindings work only when the form is loaded and when I select the second tab. This brings the suspicion to me: data bindings work only when bound controls become visible.
Anyone can tell me whether this is true or not? It is not hard to test this but I would like to know some confirmation.
Thanks
You are correct. A data-bound control are not updated until the control is made visible.
The only reference I can find for this at the moment is this MSDN thread.
Your issue has to do with the behavior of the TabControl. See Microsoft bug report. I posted a workaround for that problem which subclasses the TabControl and 'Iniatalizes' all the tab pages when the control is created or the handle is created. Below is the code for the workaround.
public partial class TabControl : System.Windows.Forms.TabControl
{
protected override void OnHandleCreated(EventArgs e_)
{
base.OnHandleCreated(e_);
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, true, Created);
}
}
protected override void OnControlAdded(ControlEventArgs e_)
{
base.OnControlAdded(e_);
System.Windows.Forms.TabPage page = e_.Control as System.Windows.Forms.TabPage;
if ((page != null) && (page.Parent == this) && (IsHandleCreated || Created))
{
InitializeTabPage(page, IsHandleCreated, Created);
}
}
protected override void OnCreateControl()
{
base.OnCreateControl();
foreach (System.Windows.Forms.TabPage tabPage in TabPages)
{
InitializeTabPage(tabPage, IsHandleCreated, true);
}
}
//PRB: Exception thrown during Windows Forms data binding if bound control is on a tab page with uncreated handle
//FIX: Make sure all tab pages are created when the tabcontrol is created.
//https://connect.microsoft.com/VisualStudio/feedback/details/351177
private void InitializeTabPage(System.Windows.Forms.TabPage page_, bool createHandle_, bool createControl_)
{
if (!createControl_ && !createHandle_)
{
return;
}
if (createHandle_ && !page_.IsHandleCreated)
{
IntPtr handle = page_.Handle;
}
if (!page_.Created && createControl_)
{
return;
}
bool visible = page_.Visible;
if (!visible)
{
page_.Visible = true;
}
page_.CreateControl();
if (!visible)
{
page_.Visible = false;
}
}
}
We've encountered a similar problem. We're trying to write to 2 bound, invisible fields so that we can change the format that we write to our dataset. This works fine when the objects are visible, but stops working when the visible property was changed to false.
To get round it, I added the following code:
// Stop our screen flickering.
chSplitContainer.Panel2.SuspendLayout();
// Make the bound fields visible or the binding doesn't work.
tbxValueCr.Visible = true;
tbxValueDb.Visible = true;
// Update the fields here.
<DO STUFF>
// Restore settings to how they were, so you don't know we're here.
tbxValueCr.Visible = false;
tbxValueDb.Visible = false;
chSplitContainer.Panel2.ResumeLayout();
I've struggled with this myself and concluded that the only workaround, besides subclassing apparently (see hjb417's answer), was to make the other tab visible. Switching to the other tab and going back to the previous immediately before the form is visible doesn't work. If you do not want to have the second tab visible, I've used the following code as a workaround:
this.tabControl.SelectedTab = this.tabPageB;
this.tabPageB.BindingContextChanged += (object sender, EventArgs e) => {
this.tabContainerMain.SelectedTab = this.tabPageA;
};
Assuming tabPageA is the visible tab, and tabPageB is the invisible one you want to initialize. This switches to pageB, and switches back once the data binding is complete. This is invisible to the user in the Form.
Still an ugly hack, but at least this works. Off course, he code gets even uglier when you have multiple tabs.
Sorry for necromancing this thread, but it is easy to force the invisible controls' databinding/handles to be ready using this method:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/190296c5-c3b1-4d67-a4a7-ad3cdc55da06/problem-with-binding-and-tabcontrol?forum=winforms
Simply, let's say if your controls are in tab page tpg_Second (or tabCtl.TabPages[1]), before you do anything with their data, call this first:
tpg_Second.Show()
This will not activate any of the tab pages, but viola, the databinding of the controls should work now.
This is not something I've come across directly. However, you might be experiencing a problem with the BindingContext. Without more details it's hard to say, but if I were you I'd set a breakpoint and make sure the controls are all bound in the same context.
Based on the answers, I made this method that works for me:
public partial class Form1: Form
{
private void Form1_Load(object sender, EventArgs e)
{
...
forceBindTabs(tabControl1);
}
private void forceBindTabs(TabControl ctl)
{
ctl.SuspendLayout();
foreach (TabPage tab in ctl.TabPages)
tab.Visible = true;
ctl.ResumeLayout();
}
}
In addition to solving the problem, the tabs are loaded at the beginning and are displayed faster when the user clicks on them.

Categories

Resources