My Silverlight application has multiple XAML pages. For example, one displays a clock, one displays a timer. I have buttons to switch back and forth like so:
private void switchRight(object sender, RoutedEventArgs e)
{
this.Content = new Clock();
}
private void switchLeft(object sender, RoutedEventArgs e)
{
this.Content = new Timer();
}
I am trying to use the NavigationService to switch back and forth so I can have other pages running in the background rather than creating a new instance each time.
I am trying
NavigationService.Navigate(new uri("/Timer.xaml", UriKind.Relative));
but it doesn't seem to do anything and I can't find any good examples to help.
Here is a link
http://blogs.msdn.com/b/dphill/archive/2009/04/28/silverlight-navigation-part-3.aspx ,
Beside, I think you can use Threading for background processes.i.e.when you start a timer no need to show any xaml.
But for page instances you need to manage it very carefully otherwise stackoverflow :)
Depending on business rules its hard to decide navigation as being in a web browser.
We created our own Wizard (with rules). You may create your own NavigationManager. For validation I can offer http://fluentvalidation.codeplex.com/
Related
I'm a fairly new to WPF and C#.
I have a frame component on my main window and 4 buttons next to it that navigate to different views in the frame. Within one the the views there is a DataGrid that has a SelectionChanged event which makes an SQL call to a database that fetches records, whose data is then used to populate a list of custom objects (these relate to the selected item on the DataGrid).
Anyways, the problem I have is that from time to time multiple calls (2 or 3) to the SelectionChanged event are being triggered at the same time for a single selection change (mouseclick) on the DataGrid.
The navigation button click events on the main window all look like this:
private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("/Views/MyDeskView.xaml", UriKind.Relative));
}
private void btn_AllOrders_Click(object sender, RoutedEventArgs e)
{
MainFrame.Navigate(new Uri("/Views/AllOrdersView.xaml", UriKind.Relative));
}
After some experiementation, I've found that the bug only happens after changing views away from the view with the DataGrid, and then changing back to it (but not always). When the bug appears the number of calls generally corresponds to the number of times I had switched views. Furthermore, the bug will simply vanish if leave the program alone for a minute or two. This makes me suspect that there multiple instances of the DataGrid view lingering like ghosts in memory and duplicating event calls until they are cleaned up by a garbage collector.
Should I be cleaning something up each time I switch views, or am I looking in the wrong place?
Thank you in advance for any help.
Edit: In answer to #Peter Moore
I subscribe to the event in the DataGrid declaration within the views XAML: SelectionChanged="dtg_MyDeskOrderGrid_SelectionChanged"
Edit: This is the sequence that happens on a selection change in the data grid. It includes several UI changes while the SQL records for the new selection are retrieved and displayed on a second DataGrid (dtg_MyDeskOrderItems). While the SQL call is being made, the relevant controls are disbaled and a semi-transparent panel (bdr_DGLoadingPanel) is moved on screen to cover them and display a loading animation. When the work is done, the work area is re-enabled and the loading panel moved off screen. Focus is also returned to the main "order" Datagrid.
dtg_MyDeskOrderGrid: This is the main DataGrid showing all "Orders"
dtg_MyDeskOrderItems: This is a secondary DataGrid that is updated to show all "Items" in the selected order.
private void dtg_MyDeskOrderGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
CurrSelectedOrder = (Order_class)dtg_MyDeskOrderGrid.SelectedItem;
if (CurrSelectedOrder.ItemList == null)
{
if (NowWorking == false)
{
NowWorking = true;
bdr_DGLoadingPanel.Margin = new Thickness(2);
dtg_MyDeskOrderGrid.IsEnabled = false;
bdr_FilterPanel.IsEnabled = false;
bdr_DGLoadingPanel.Focus();
img_LoadingCircle.RenderTransform = rt;
img_LoadingCircle.RenderTransformOrigin = new Point(0.5, 0.5);
da.RepeatBehavior = RepeatBehavior.Forever;
rt.BeginAnimation(RotateTransform.AngleProperty, da);
bdr_DGLoadingPanel.UpdateLayout();
worker.RunWorkerAsync();
}
}
else
{
dtg_MyDeskOrderItems.ItemsSource = null;
dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
dtg_MyDeskOrderItems.Items.Refresh();
}
}
private void worker_DoWork(object? sender, DoWorkEventArgs e)
{
DatabaseConnection DBConn9 = new DatabaseConnection();
DBConn9.FillOrderItems(CurrSelectedOrder);
}
private void worker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
dtg_MyDeskOrderItems.ItemsSource = null;
dtg_MyDeskOrderItems.ItemsSource = CurrSelectedOrder.ItemList;
dtg_MyDeskOrderItems.Items.Refresh();
bdr_DGLoadingPanel.Margin = new Thickness(1000, 2, 2, 2);
rt.BeginAnimation(RotateTransform.AngleProperty, null);
dtg_MyDeskOrderGrid.IsEnabled = true;
bdr_FilterPanel.IsEnabled = true;
// The following work-around and accompanying GetDataGridCell function were used to give keyboard focus back to the datagrid to make navigation with arrow keys work again.
// It appears keyboard focus is not returned to the Datagrid cells when using the Datagrid.focus() method.
Keyboard.Focus(GetDataGridCell(dtg_MyDeskOrderGrid.SelectedCells[0]));
NowWorking = false;
});
}
Edit...
Following the advice of the commentors, I was able to fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:
private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
{
dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
}
However, I was not able to reproduce the bug using a barebones testing project.
The UI in the original project is quite heavy, so I'm wondering if it's not the old view and events lingering in memory as this seems to fit the behavior of the bug & fix (Only occuring when I navigate away and back causing a new view to be created, multiple event triggers corresponding to the number of times I navigated away, and then finally the bug vanishing of its own accord after a moment or two).
I won't be settling on this as a final solution and instead will learn about ways I can reuse instances of my views (as suggested by Bionic) instead of recreating them. The reason for this is, if the SelectionChanged event is getting multiple triggers from old view instances, then it is likely other events will suffer from the same bug. This would be bad.
#BionicCode If you are still around, could you repost your initial comment as a solution so I can mark it answered?
Thank you to everyone for all the help and education. ^_^
Following the advice of the commentors, I was able to initially fix the bug by unsubscribing from the event in the Unloaded event for the view containing the DataGrid:
private void uct_MyDeskView_Unloaded(object sender, RoutedEventArgs e)
{
dtg_MyDeskOrderGrid.SelectionChanged -= dtg_MyDeskOrderGrid_SelectionChanged;
}
However as this solution only disconnected the one event and didn't solve the root problem of old views lingering in the background and firing events before being cleaned up, I finally went the following code that keeps only one instance of my view in memory and reuses it.
private MyDeskView? currMyDeskView = null;
private void btn_MyDesk_Click(object sender, RoutedEventArgs e)
{
if (currMyDeskView == null)
{
currMyDeskView = new MyDeskView();
}
MainFrame.Navigate(currMyDeskView);
}
Thank you to everyone who helped me get over this bug.
I currently have a MainWindow that acts as a frame to navigate to other pages in my solution. The problem is, i require one of my pages to be instantiated for the entire duration of my application instead of every time when i navigate to a page, that page gets re-instantiated. I have tried the KeepAlive='true' property for my page but it did not work.
I would like to know if theres a way to implement "this static instance of a page" method for my codes. Thanks. (p.s im not looking or planning to implement the MVVM approach)
public MainWindow()
{
InitializeComponent();
//Instanciate ApiStartup class and Initialize the HTTPClient
ApiStartup.InitializeClient();
Application.Current.MainWindow = this;
Loaded += OnMainWindowLoaded;
}
private void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
ChangeView(new DetectionPage());
}
public void ChangeView(Page view)
{
MainFrame.NavigationService.Navigate(view);
}
private void quiz_Click(object sender, MouseButtonEventArgs e)
{
var mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow?.ChangeView(new DetectionPage());
}
If you want to use static value for use in entire application from start to end then you can mention it in app.config file. And easily you can use like ((App)Application.current).KeepAlive in your any xaml file.
A page has the keepalive property.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You may set that to true on whichever page you like.
When you later use navigation methods to navigate to that url, the first instance of the page will be returned.
If that doesn't suit for whatever reason you could cache instances of pages using a dictionary with key type and value page (instance). Then implement some logic decides which pages you do or do not cache in there.
You said you don't want to use mvvm but I think it best to at least mention what almost everyone else does. For other people who intend working in a team then MVVM will be expected and hence for other people reading:
Since pages and frames come with a journal and memory overheads that are often undesirable, most teams (I have encountered) use usercontrols rather than pages. These are presented via a contentcontrol. You can set content to a usercontrol but most teams use viewmodel first. State that matters is bound and templated from an instance of a viewmodel. To retain state between "navigations" a reference to the viewmodel is retained - which is almost guaranteed to be way lighter on memory than any sort of view.
i want to navigate txtBox1.text and bg2.source to another xaml at the same time
i try to use this
private void button1_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(MultiGame), bg2.Source);
this.Frame.Navigate(typeof(MultiGame), txtBox1.text);
}
it doesnt work. I hope someone can help me!!!
Of course this does not work. As you call the Navigate method multiple times the Page MultiGameis loaded 2 times with each one of the parameters.
Why don`t you pack the two variables in one object array, and pass this to the navigate method?
Like
this.Frame.Navigate(typeof(MultiGame),new object[] {bg2.Source, txtBox1.text});
In the MultiGame class you now have to watch to the PageNavigated event. The navigation data is now your array.
I have a project that I'm doing with
Microsoft VSTO (office 2013 excel)
I have certain things that make calls that take maybe 10 seconds to come back.
Ideally I would like to display an progress bar or some status... After a lot of searching I found an article that is titled:
How do I create a splash screen window for the VSTO applications?
http://www.datazx.cn/Fv7p5a/xw/oa2v/2q7xs6/mcccjfti-988m-f8r8-8d44-bstb4rfsi4xm23rsdfd.html
So I started creating this code in a form, but then I realize that I need to call it up within my methods and really attach events etc...
The article says to
"display a modal form on a background thread" What is the best way to do this?
I find it easier to use modal less form on main thread and so far haven't seen any problem with modal less approach. Something like code below
var splashWindow = new SplashWindow();
splashWindow.Show();
splashWindow.SetMessage("Starting please wait...");
DoSomeWork(splashWindow);
splashWindow.Close();
Following you will see a way I programmed a Splash Screen for Excel-VSTO in C#. My Excel file is enabled for macros (.xlsm). These are the steps:
Create your splash screen. Let's assume the name of the form is SplashScreen.
Go to the code of the object ThisWorkbook.cs
Check the code looks like:
public partial class ThisWorkbook
{
SplashScreen SC = new SplashScreen();
private async void ThisWorkbook_Startup(object sender, System.EventArgs e)
{
SC.Show();
await Task.Delay(3500);
SC.Close();
more code...
}
}
It is important that you notice that I added the word async to the subroutine.
private void ThisWorkbook_Startup(object sender, System.EventArgs e)
I hope this is very useful.
I'm trying to write a silverlight application that takes in InitParams and then uses those InitParams to make a change to the Source of the MediaElement on the page. I'm trying to figure out the proper place to put my code.
I watched Tim Heuer's excellent video on InitParams, but in the video (which was for Silverlight 2), it shows the following on the Page.xaml.cs:
void Page_Loaded(object sender, RoutedEventArgs e)
{
}
I don't see Page_Loaded when I open MainPage.xaml.cs, and I'm wondering if that was automatically created in the Silverlight 2 SDK and left out of the Silverlight 3 SDK. Or perhaps Tim added that in his video manually.
I find that I can go into the opening UserControl tag of MainPage.xaml and add Loaded="<New_Event_Handler>" which creates the following in MainPage.xaml.cs:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
}
By default, there's also the following in MainPage.xaml.cs, which is run during the Application_Startup event in App.xaml.cs:
public MainPage()
{
InitializeComponent();
}
I need to figure out where is the best place to insert my code to change the Source on my MediaElement in my xaml. Should I put it in MainPage? Should I add the Loaded event handler and put it into UserControl_Loaded? If it's supposed to be Page_Loaded, where do I find that in Silverlight 3?
Any help would be much appreciated.
"UserControl_Loaded" and "Page_Loaded" are just method names and the names don't matter (you could name the method "Foo" if you wanted). What makes these methods do anything is the fact that they are attached to the Loaded event on the UserControl (which is what you did when you edited the MainPage.xaml file).