SwipeRefreshLayout return bug - c#

I'm using a SwipeRefeshLayout with a RecycleView inside.
This recycleView contains view-holders that have a click function that changes the display. This works fine until I refresh the view multiple times.
For example: If I refresh it 5 times, click on a viewholder, I will have to click 5 times on the return button to return to the recycleview fragment.
The code:
HomeFragment.cs:
private void HandleRefresh(object sender, EventArgs e)
{
try
{
page = 0;
adapter.clearMoments();
RefreshData(adapter, 0);
mySwipeRefreshLayout.Refreshing = false;
}
private async void RefreshData(MomentAdapterRV adapter, int page)
{
JsonValue json = await model.getMoments(page);
try
{
InitData(adapter, json, page);
}
private void InitData(MomentAdapterRV adapter, JsonValue json, int pageNum)
{
var myActivity = (MainActivity)this.Activity;
try
{
if (json.Count > 0)
{
for (var i = 0; i < json.Count; i++)
{
// Some code
adapter.addMoment(Moment moment)
}
// Some code
}
}
MomentAdapterRV.cs:
public MomentAdapterRV(Context context, List<Moment> items, MainActivity activity)
{
mItems = items;
mContext = context;
mActivity = activity;
cb = CommunityBuilderModel.Instance;
}
/// <summary>
/// This is the constuctor function of this adapter where the given class arguments (streamFragment, Moments) are being passed to the class variables.
/// </summary>
/// <param name="streamFragement"></param>
/// <param name="mItems"></param>
public MomentAdapterRV(StreamFragment streamFragement, List<Moment> mItems)
{
this.streamFragement = streamFragement;
this.mItems = mItems;
}
public void addMoment(Moment moment)
{
mItems.Add(moment);
NotifyDataSetChanged();
}
public void clearMoments()
{
mItems.Clear();
}
public override RecyclerView.ViewHolder
OnCreateViewHolder(ViewGroup parent, int viewType)
{
View itemView = LayoutInflater.From(parent.Context).
Inflate(Resource.Layout.MomentListItem, parent, false);
MomentViewHolder vh = new MomentViewHolder(itemView);
return vh;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
// Some code
vh.llMain.Click += (object sender, EventArgs e) =>
{
//Check if you don't get a negative position from the header.
if (holder.AdapterPosition >= 0)
{
// Create a new fragment and a transaction.
FragmentTransaction fragmentTx = mActivity.FragmentManager.BeginTransaction();
MomentFragment aDifferentDetailsFrag = new MomentFragment();
// Some code
// Replace the fragment that is in the View fragment_container (if applicable).
fragmentTx.Replace(Resource.Id.frameLayout1, aDifferentDetailsFrag);
// Add the transaction to the back stack.
fragmentTx.AddToBackStack(null);
// Commit the transaction.
fragmentTx.Commit();
//Put Argument
aDifferentDetailsFrag.Arguments = utilBundle;
}
};
}
}
}

Your problem is that you assign the Click event in OnBindViewHolder and you never unsubscribe this click event.
So for each time you refresh your items in your RecyclerView, OnBindViewHolder is called. This will also happen if your list is longer than can be displayed on screen and you scroll up and down.
Instead you should assign the Click even in OnCreateViewHolder so it is only hooked up once, and not every time it is shown.

Related

Removing item from ListBox - unhandled exception in WPF?

I have this code in my MainWindow.
The user enters Name, Phone and Email in the fields provided, selects Location and then the Name appears in the listbox, lstClients.
I'm trying to write a method to remove a selected name from the listbox.
namespace WpfApp_Employment_Help
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// client list
List<Client> ClientList = new List<Client>();
public MainWindow()
{
InitializeComponent();
}
// method to select location via radio button
public string GetSelectedLocation()
{
string selected = string.Empty;
if (RBLocE.IsChecked == true) { selected = "Edinburgh"; }
else if (RBLocG.IsChecked == true) { selected = "Glasgow"; }
else if (RBLocO.IsChecked == true) { selected = "Other"; }
return selected;
}
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
if (c != null)
{
c.AssignID();
}
lstClients.ItemsSource = null;
lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
lstClients.Items.Remove(lstClients.SelectedItem);
}
}
}
When I run this code, I get Unhandled Exception:
System.InvalidOperationException: 'Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.'
how can I rewrite my RemoveClient method?
my code for the Client class is this:
public partial class Client
{
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Location { get; }
public bool IDed { get; private set; }
public Client(string n, string p, string e, string l)
{
Name = n;
Phone = p;
Email = e;
Location = l;
}
}
I have Visual Studio 2022 which has been recently updated.
I have also tried the following solution but it gives me another unhandled error?
It looks like I need to change List </string/> and string to something else. but what?
private void RemoveClient(object sender, EventArgs e)
{
if (lstClients.Items.Count >= 1)
{
if (lstClients.SelectedValue != null)
{
var items = (List<string>)lstClients.ItemsSource;
var item = (string)lstClients.SelectedValue;
lstClients.ItemsSource = null;
lstClients.Items.Clear();
items.Remove(item);
lstClients.ItemsSource = items;
}
}
else
{
System.Windows.Forms.MessageBox.Show("No ITEMS Found");
}
}
System.InvalidCastException: 'Unable to cast object of type 'System.Collections.Generic.List`1[WpfApp_Employment_Help.Client]' to type 'System.Collections.Generic.List`1[System.String]'.'
As the error message suggests, you can't modify the ItemsControl via the collection view returned from the ItemsControl.Items property.
WPF is generally designed to work on the data sources instead of handling the data related controls (data presentation). This way data and data presentation (GUI) are cleanly separated and code will become a lot simpler to write.
In case of the ListView (or ItemsControl in general), simply modify the source collection.
To improve performance, the source collection should be a INotifyCollectionChanged implementation, for example ObservableCollection<T>, especially when you expect to modify the source collection.
This makes invalidating the ItemsSource e.g. by assigning null, just to set it again redundant and significantly improves the performance.
public partial class MainWindow : Window
{
// client list
public ObservableCollection<Client> ClientList { get; } = new ObservableCollection<Client>();
// method to create a new client on click
private void newClient(object sender, RoutedEventArgs e)
{
Client c = new Client(boxClientName.Text, boxClientPhone.Text, boxClientEmail.Text, GetSelectedLocation());
boxClientName.Clear();
boxClientPhone.Clear();
boxClientEmail.Clear();
ClientList.Add(c);
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to id selected client
private void AssignID(object sender, RoutedEventArgs e)
{
Client c = lstClients.SelectedItem as Client;
// Same as the if-statement below
c?.AssignID();
//if (c != null)
//{
// c.AssignID();
//}
// The following lines are no longer needed
// as the GUI is now notified about the collection changes (by the INotifyCollectionChanged collection)
//lstClients.ItemsSource = null;
//lstClients.ItemsSource = ClientList;
}
// method to remove selected client
private void RemoveClient(object sender, RoutedEventArgs e)
{
var clientToRemove = lstClients.SelectedItem as Client;
this.ClientList.Remove(clientToRemove);
}
}
If you change the type of ClientList from List<Client> to ObservableCollection<Client>, you could simply remove the item directly from the source collection:
public partial class MainWindow : Window
{
ObservableCollection<Client> ClientList = new ObservableCollection<Client>();
public MainWindow()
{
InitializeComponent();
}
...
private void RemoveClient(object sender, RoutedEventArgs e)
{
ClientList.Remove(lstClients.SelectedItem as Client);
}
}

How can I await for a button click in an async method?

I try to write a code to read a JSON File and allows user to input all the parametes for the objects in the JSON File one by one.
I try to write something like an "awaitable Button", but I failed to write a "GetAwaiter" extension for the button, although I found informations about how to do it.
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-inherit-from-existing-windows-forms-controls?view=netframeworkdesktop-4.8
how can I combine await.WhenAny() with GetAwaiter extension method
http://blog.roboblob.com/2014/10/23/awaiting-for-that-button-click/
So here is my code after clicking a button "loadJSON":
for (int i = 0; i<templist_net.Count; i++)
{
GeneratorFunctions.GetNetworkParameterList(templist_net[i].Type, templist_net[i], treeViewPath.SelectedPath, SolutionFolder);
cBoxPouItem.Text = templist_net[i].Type;
ListViewParam2.ItemsSource = GeneratorFunctions.TempList; // Parameter list binding
temp = GeneratorFunctions.TempList;
ListViewParam2.Visibility = Visibility.Visible; // Set list 2 visible
ListViewParam.Visibility = Visibility.Collapsed; // Set list 1 invisible
//something stop loop, and wait user to type parameters in Listview, and click Button, Then the loop move on.
}
And Here is code trying to write a Button with extension. I add a new class for custom control, and write the extension.
public partial class CustomControl2 : System.Windows.Forms.Button
{
static CustomControl2()
{
}
public static TaskAwaiter GetAwaiter(this Button self)
{
ArgumentNullException.ThrowIfNull(self);
TaskCompletionSource tcs = new();
self.Click += OnClick;
return tcs.Task.GetAwaiter();
void OnClick(object sender, EventArgs args)
{
self.Click -= OnClick;
tcs.SetResult();
}
}
}
But I can't write a extension, which inherit System.Windows.Forms.Button. What should I do?
UPDATE:
here is what i tried.
private async Task Btn_loadJsonAsync(object sender, RoutedEventArgs e) {
// Initialize an open file dialog, whose filter has a extend name ".json"
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "(*.json)|*.json";
TextBoxInformation.Text += "Opening project ...\n";
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
networks = GeneratorFunctions.ReadjsonNetwork(openFileDialog.FileName);
for (int i = 0; i < networks.Count; i++)
{
if (temp != null)
{
if (networks[i].Type == "Network")
{
templist_net.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Subsystem")
{
templist_sub.Add(networks[i]);
i = 1;
}
if (networks[i].Type == "Component: Data Point Based Control")
{
templist_com.Add(networks[i]);
i = 1;
}
}
}
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}}}
You can wait for a button click asynchronously using a SemaphoreSlim, e.g.:
using (SemaphoreSlim semaphore = new SemaphoreSlim(0, 1))
{
void OnClick(object sender, RoutedEventArgs e) => semaphore.Release();
btn.Click += OnClick;
for (int i = 0; i < templist_net.Count; i++)
{
//...
//wait here until [btn] is clicked...
await semaphore.WaitAsync();
}
btn.Click -= OnClick;
}
Although you may want to redesign the way you are doing things, a quick an dirty solution would be to use a dialog box in modal mode and upon the dialog box closing, capture the data that got input and continue looping. The loop will block until the dialog box is closed.
First of all I must insist that your request goes against the principles of the MVVM pattern which is based on events.
Your logic should be in a separate class and expose an OnNext method which should be called from the model through an ActionCommand
Anyway, to conform (as much as possible) to the MVVM pattern, you don't want to await on the button but more on the command bound to the button.
So let's build an awaitable command :
public class AwaitableCommand : ICommand
{
private readonly object _lock = new();
private TaskCompletionSource? _taskCompletionSource;
/// <summary>
/// null-event since it's never raised
/// </summary>
public event EventHandler? CanExecuteChanged
{
add { }
remove { }
}
/// <summary>
/// Always executable
/// </summary>
public bool CanExecute(object? parameter) => true;
public void Execute(object? parameter)
{
lock (_lock)
{
if (_taskCompletionSource is null)
return;
_taskCompletionSource.SetResult();
// reset the cycle
_taskCompletionSource = null;
}
}
public Task WaitAsync()
{
lock (_lock)
{
// start a new cycle if needed
_taskCompletionSource ??= new TaskCompletionSource();
return _taskCompletionSource.Task;
}
}
}
Then you can create your logic with it (I put it in the model, wich is a bad practice):
public class Model : NotifyPropertyChangedBase
{
private int _count;
public Model()
{
RunLogicAsync();
}
public int Count
{
get => _count;
private set => Update(ref _count, value);
}
public AwaitableCommand OnNextCommand { get; } = new();
/// <summary>
/// I know, I know, we should avoid async void
/// </summary>
private async void RunLogicAsync()
{
try
{
for (;;)
{
await OnNextCommand.WaitAsync();
Count++;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
And your view:
<Window ...>
<Window.DataContext>
<viewModel:Model />
</Window.DataContext>
<Window.Resources>
<system:String x:Key="StringFormat">You clicked it {0} times</system:String>
</Window.Resources>
<Grid>
<Button Content="{Binding Count}"
ContentStringFormat="{StaticResource StringFormat}"
Command="{Binding OnNextCommand}"
Padding="10 5"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Window>
Working demo available here.

Idling and ExternalEvent are not raised while an Element is selected

I'm building a Revit AddIn with WPF modeless dialogs and I want to use an ExternalEvent to retrieve Elements selected by the user. Is what I am doing viable and what do I need to change for it to work?
Since I don't have a valid API document context, I raise an ExternalEvent when a button is clicked to retrieve UniqueId of Elements that are currently selected.
Here are the relevant classes (I tried to reduce the code as much as I could) :
public class App : IExternalApplication {
internal static App _app = null;
public static App Instance => _app;
public Result OnStartup(UIControlledApplication application) {
_app = this;
return Result.Succeeded;
}
public void ShowWin(UIApplication ui_app) {
var eventHandler = new CustomEventHandler();
var externalEvent = ExternalEvent.Create(eventHandler);
var window = new WPFWindow(eventHandler, externalEvent);
Process proc = Process.GetCurrentProcess();
WindowInteropHelper helper = new WindowInteropHelper(window) {
Owner = proc.MainWindowHandle
};
window.Show();
}
}
public class AddIn : IExternalCommand {
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) {
App.Instance.ShowWin(commandData.Application);
return Result.Succeeded;
}
}
public class CustomEventHandler : IExternalEventHandler {
public event Action<List<string>> CustomEventHandlerDone;
public void Execute(UIApplication ui_app) {
UIDocument ui_doc = ui_app.ActiveUIDocument;
if (ui_doc == null) {
return;
}
Document doc = ui_doc.Document;
List<string> element_ids = null;
var ui_view = ui_doc.GetOpenUIViews().Where(x => x.ViewId == doc.ActiveView.Id).FirstOrDefault();
if (doc.ActiveView is View3D view3d && ui_view != null) {
using (Transaction tx = new Transaction(doc)) {
tx.Start();
element_ids = ui_doc.Selection.GetElementIds().Select(x => doc.GetElement(x)?.UniqueId).Where(x => x != null).ToList();
tx.Commit();
}
}
this.CustomEventHandlerDone?.Invoke(element_ids);
}
}
public partial class WPFWindow {
private CustomEventHandler _eventHandler;
private ExternalEvent _externalEvent;
public WPFWindow(CustomEventHandler eventHandler, ExternalEvent externalEvent) {
this._eventHandler = eventHandler;
this._eventHandler.CustomEventHandlerDone += this.WPFWindow_CustomEventDone;
this._externalEvent = externalEvent;
}
private void Button_Click(object sender, RoutedEventArgs e) {
this._externalEvent.Raise();
}
private void WPFWindow_CustomEventDone(List<string> element_ids) {
// this point is never reached while an element is selected
}
}
When an element is selected, the ExternalEvent is marked as pending but is only executed when the selection is cleared by the user.
The same happens with UIControlledApplication.Idling.
I would like for it to be executed even when elements are selected, or an alternative way to do it, and not involving PickObject.
I ran into the same problem.
I was able to determine that the problem occurs if elements of the same family are selected. Moreover, there is a certain threshold value, somewhere from 10 to 20 or more, at which this is manifested.
I was able to get around this by canceling the selection of elements UIDocument.Selection.SetElementIds(new List<ElementId>()) before calling ExternalEvent.Raise(). And then at the end return the selection, if necessary.

SelectedIndex on ComboBox to first item on data change

I'm learning UWP in VS2015 Community right now and having trouble with one section in regards to a ComboBox and could really use some help.
I'm writing a Bible app and have 3 ComboBoxes for Translation, Book, and Chapter. When I change the Book dropdown it should change the Chapter to 1. At least until I make a forward and back button for chapters, just covering the basics right now. When I change the Translation let's say from NIV to KJV it should change to the currently selected Book/Chapter in that translation.
I've preloaded the texts from XML and loaded them into an object called dataLoader. I'm doing selections on it via LINQ in the code below.
So right now I say something like:
private void DataLoader_Completed(object sender, EventArgs e)
{
dataLoaded = true;
cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
cmb_Chapter.ItemsSource = from c in dataLoader.Translations[0].Books[0].Chapters select new { c.Index };
cmb_Book.SelectedIndex = 0;
cmb_Translation.SelectedIndex = 0;
cmb_Chapter.SelectedIndex = 0;
}
private void translationChanged()
{
chapterChanged();
}
private void bookChanged()
{
cmb_Chapter.ItemsSource = from c in dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters select new { c.Index };
cmb_Chapter.SelectedIndex = 0;
}
private void chapterChanged()
{
textBlock_Verses.Text = dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters[cmb_Chapter.SelectedIndex].TextLineSeparated;
}
private void cmb_Translation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
translationChanged();
}
private void cmb_Book_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
bookChanged();
}
private void cmb_Chapter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
chapterChanged();
}
I'm getting errors back though on the first run that the index is out of range because at first the SelectedIndex of the translation is -1, if I run translation first it will give me an out of range on the book for SelectedIndex being -1.
I want the selected index changing to trigger the proper events but as you can see that's not going to work how it is now. Also the code is pretty messy, I've started looking a bit into Binding but there are a lot of hurdles like figuring out how to bind to a property that returns a LINQ result. I'm not sure how to move forward on this and definitely appreciate any help I can get.
Combobox can have no selection - selected item is null and this is how it's initialized, so before you set SelectedInexes all are null (this means that SelectedIndex == -1):
private void DataLoader_Completed(object sender, EventArgs e)
{
dataLoaded = true;
cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
cmb_Chapter.ItemsSource = from c in dataLoader.Translations[0].Books[0].Chapters select new { c.Index };
cmb_Book.SelectedIndex = 0; // <- you set here selected index for book
// which fires bookchanged even right away
// In that event cmb_Translation.SelectedIndex
// is still -1 which will surely throw exception
cmb_Translation.SelectedIndex = 0;
cmb_Chapter.SelectedIndex = 0;
}
You probably should put some check-ups if values are properly set before using them. Also think if there is a chance when there is no selection state.
private void bookChanged()
{
if (cmb_Translation.SelectedIndex >= 0)
{
cmb_Chapter.ItemsSource = from c in dataLoader.Translations[cmb_Translation.SelectedIndex].Books[cmb_Book.SelectedIndex].Chapters select new { c.Index };
cmb_Chapter.SelectedIndex = 0;
}
}
There is one hiccup with this - you will have to launch bookchanged() at the endo of DataLoader_Completed manually, as it won't process before, due to -1.
Here's the solution I came to that works, although I think maybe a better solution could have been achieved using Binding to properties I've created, some feedback on improvements could always be helpful from a design perspective. What I did essentially was create an UpdateChapterText Boolean property that when set to false won't process my SelectedIndex change events, but will still allow me to update underlying data sources and change the SelectedIndex on the fly, that way I can process events in one go, otherwise it might be fire multiple events to update the underlying data sources. I needed to be able to do this because for the chapter text view it relies on both the translation and book selected indexes to be set, but the default behavior only allows one or the other to be set before events process, which becomes unsolvable at least I think for now unless you turn off events processing I found.
/// <summary>
/// This is a helper property for setting the Translation SelectedIndex
/// </summary>
private int TranslationIndex
{
get
{
return cmb_Translation.SelectedIndex;
}
set
{
cmb_Translation.SelectedIndex = value;
}
}
/// <summary>
/// This is a helper property for setting the Book SelectedIndex
/// </summary>
private int BookIndex
{
get
{
return cmb_Book.SelectedIndex;
}
set
{
cmb_Book.SelectedIndex = value;
}
}
/// <summary>
/// This is a helper property for setting the Chapter SelectedIndex
/// </summary>
private int ChapterIndex
{
get
{
return cmb_Chapter.SelectedIndex;
}
set
{
cmb_Chapter.SelectedIndex = value;
}
}
/// <summary>
/// Retrieves the currently selected Chapter listing
/// </summary>
public IEnumerable<ChapterIndex> CurrentChapters
{
get
{
return from c in dataLoader.Translations[TranslationIndex].Books[BookIndex].Chapters select new ChapterIndex { Index = c.Index };
}
}
/// <summary>
/// Retrieves Genesis in the first loaded translation
/// </summary>
public IEnumerable<ChapterIndex> Genesis
{
get
{
return from c in dataLoader.Translations[0].Books[0].Chapters select new ChapterIndex { Index = c.Index };
}
}
public IEnumerable<BookNames> CurrentBooks
{
get
{
return from b in dataLoader.Translations[TranslationIndex].Books select new BookNames { BookName = b.BookName };
}
}
/// <summary>
/// Allows events to process on ComboBoxes and Back/Forward Buttons
/// to change Chapters, you usually don't want to do this lots of
/// times in one second if changing the Translation/Book/Chapter
/// all at one time so you may set it to false first, update your
/// variables, and then set it back to true so updating events will
/// process correctly.
/// </summary>
public bool UpdateChapterText { get; set; }
/// <summary>
/// The DataLoader object loads up the various Bible translation texts
/// </summary>
TheBible.Model.DataLoader.DataLoader dataLoader;
public MainPage()
{
this.InitializeComponent();
dataLoader = new Model.DataLoader.DataLoader();
dataLoader.Completed += DataLoader_Completed;
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();
}
private void DataLoader_Completed(object sender, EventArgs e)
{
UpdateChapterText = false;
cmb_Translation.ItemsSource = from t in dataLoader.Translations select new { t.TranslationShortName };
cmb_Book.ItemsSource = from b in dataLoader.Translations[0].Books select new { b.BookName };
cmb_Chapter.ItemsSource = Genesis;
cmb_Translation.SelectedIndex = 0;
cmb_Book.SelectedIndex = 0;
UpdateChapterText = true;
cmb_Chapter.SelectedIndex = 0;
}
private void translationChanged()
{
chapterChanged();
}
private void bookChanged()
{
UpdateChapterText = false;
cmb_Chapter.ItemsSource = CurrentChapters;
UpdateChapterText = true;
cmb_Chapter.SelectedIndex = 0;
}
private void chapterChanged()
{
textBlock_Verses.Text = dataLoader.Translations[TranslationIndex].Books[BookIndex].Chapters[ChapterIndex].TextLineSeparated;
}
private void decrementChapter()
{
UpdateChapterText = false;
if (this.cmb_Chapter.SelectedIndex == 0)
{
if (this.cmb_Book.SelectedIndex > 0)
{
this.cmb_Book.SelectedIndex--;
UpdateChapterText = true;
this.cmb_Chapter.SelectedIndex = CurrentChapters.Count() - 1;
}
}
else
{
UpdateChapterText = true;
this.cmb_Chapter.SelectedIndex--;
}
UpdateChapterText = true;
}
private void incrementChapter()
{
UpdateChapterText = false;
if (this.cmb_Chapter.SelectedIndex == this.cmb_Chapter.Items.Count - 1)
{
if (this.cmb_Book.SelectedIndex < this.cmb_Book.Items.Count - 1)
{
this.cmb_Book.SelectedIndex++;
UpdateChapterText = true;
this.cmb_Chapter.SelectedIndex = 0;
}
}
else
{
UpdateChapterText = true;
this.cmb_Chapter.SelectedIndex++;
}
UpdateChapterText = true;
}
private void cmb_Translation_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (UpdateChapterText)
translationChanged();
}
private void cmb_Book_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (UpdateChapterText)
bookChanged();
}
private void cmb_Chapter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (UpdateChapterText)
chapterChanged();
}
private void btn_Forward_Click(object sender, RoutedEventArgs e)
{
incrementChapter();
}
private void btn_Back_Click(object sender, RoutedEventArgs e)
{
decrementChapter();
}
private void btn_FullScreen_Click(object sender, RoutedEventArgs e)
{
var view = ApplicationView.GetForCurrentView();
if (view.IsFullScreenMode)
{
sym_FullScreen.Symbol = Symbol.FullScreen;
view.ExitFullScreenMode();
}
else
{
sym_FullScreen.Symbol = Symbol.BackToWindow;
view.TryEnterFullScreenMode();
}
}

Windows store app navigation

I'm making a Pacman windows store app game. I use win2d library to make animations. I have a problem in navigation between pages.
Here's my main page, it creates new Game.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
//Game gm = new Game();
}
private void playButton_Click(object sender, RoutedEventArgs e)
{
Game gm = new Game();
}
private void exitButton_Click(object sender, RoutedEventArgs e)
{
Application.Current.Exit();
}
private void resultsButton_Click(object sender, RoutedEventArgs e)
{
}
}
but in Game class when end finishes I have to somehow comeback to my main page. I have tried many ways but they doesn't work for me.
Game class:
public Game()
{
this.InitializeComponent();
Window.Current.Content = this;
}
private void canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}
async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
ghostImages = new List<CanvasBitmap>();
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost1.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost2.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost3.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost4.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/Pacman_25.png")));
StartNewGame();
}
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
Map.drawBorders(args);
using (var session = args.DrawingSession)
{
session.DrawImage(hero.getImage1(), hero.getX(), hero.getY());
for (int i = 0; i < ghostList.ToArray().Length; i++)
{
session.DrawImage(ghostList[i].getImage(), ghostList[i].getX(), ghostList[i].getY());
}
int bestScore = 1, score = 3;
session.DrawText("Rekordas: " + bestScore, Constants.WIDTH / 3 * 1.8f, Constants.HEIGHT + Constants.SHOWINFOSIZE / 2, Windows.UI.Colors.White);
session.DrawText("Rezultatas: " + score, Constants.BLOCKSIZE, Constants.HEIGHT + Constants.SHOWINFOSIZE / 2, Windows.UI.Colors.White);
session.DrawText("Gyvybės: ", Constants.BLOCKSIZE, Constants.HEIGHT + Constants.SHOWINFOSIZE / 1, Windows.UI.Colors.White);
for (int i = 0; i < 3; i++)
session.DrawImage(hero.getImage1(), Constants.BLOCKSIZE + 150 + (Constants.BLOCKSIZE + 5) * i, (int)Constants.HEIGHT + Constants.SHOWINFOSIZE / 1 - Constants.BLOCKSIZE + 5);
}
}
public void GameOver()
{
playing = false;
//Frame.Navigate(typeof(MainPage));
//Dispose();
//this.Dispose();
//var page = new MainPage();
//Window.Current.Content = page;
//MainPage mn = new MainPage();
//if (name == null)
//{
// name = "Student";
//}
//Window.Current.Content = new MainPage();
//mn.UpdateLayout();
}
How can I navigate through pages? Thanks.
Here are some methods that you might find helpful (from a class that I use to wrap navigation logic inside)
//Better made the class a singleton but I've skipped that part to for brifety
public class Navigation
{
public bool CanGoBack
{
get
{
var frame = ((Frame)Window.Current.Content);
return frame.CanGoBack;
}
}
public Type CurrentPageType
{
get
{
var frame = ((Frame)Window.Current.Content);
return frame.CurrentSourcePageType;
}
}
public virtual void GoBack()
{
var frame = ((Frame)Window.Current.Content);
if (frame.CanGoBack)
{
frame.GoBack();
}
}
public virtual void NavigateTo(Type sourcePageType)
{
((Frame)Window.Current.Content).Navigate(sourcePageType);
}
public virtual void NavigateTo(Type sourcePageType, object parameter)
{
((Frame)Window.Current.Content).Navigate(sourcePageType, parameter);
}
public virtual void GoForward()
{
var frame = ((Frame)Window.Current.Content);
if (frame.CanGoForward)
{
frame.GoForward();
}
}
}
You use it like this (if we assume the aforementioned methods reside in a class named Navigation that you have instance of):
//To go to Game page
Navigation.NavigateTo(typeof(Game));
//To go to Main page and pass some arguments
Navigation.NavigateTo(typeof(MainPage), winnerId);
//To go back
Navigation.GoBack();
Addition
You could receive your passed parameters in your views like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var receivedParameter = e.Parameter as TheTypeOfThePassedParameter;
}
Additional option to pass data is to create one static or singleton application-wise class (visible from everywhere) containing some values that you want available throughout your app
I suggest you to consider Model View View Model pattern to manage your App's navigation logic and contents. (Channel9 introductive video)
To help you with navigation, you could use MVVM Light library that exposes some useful navigation methods:
In ViewModelLocator.cs you could define a string for every page to be navigated:
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
var nav = new NavigationService();
nav.Configure("MainMenu", typeof(MainMenuView));
nav.Configure("About", typeof(AboutView));
nav.Configure("Game", typeof(GameView));
SimpleIoc.Default.Register<INavigationService>(() => nav);
SimpleIoc.Default.Register<MainMenuViewModel>();
SimpleIoc.Default.Register<AboutViewModel>();
SimpleIoc.Default.Register<GameViewModel>();
}
A typical ViewModel could be:
public class GameViewModel : ViewModelBase
{
private INavigationService _navigationService;
public GameViewModel(INavigationService NavigationService)
{
_navigationService = NavigationService;
}
// Then, when you want to expose a navigation command:
private RelayCommand _navigateToMenuCommand;
public RelayCommand NavigateToMenuCommand
{
get
{
return _navigateToMenuCommand
?? (_navigateToMenuCommand = new RelayCommand(
() =>
{
_navigationService.NavigateTo("MainMenu");
}
{
}
}
And .XAML:
<Button Content="Back to Main Menu" Command={Binding GameViewModel} />

Categories