I have started working with WPF MVVM Light and now I'am trying to navigate between pages.
In the MainWindow I have added a "BackButton"
<Button Command='{Binding Main.GoBack, Mode=OneWay}' />
which is binding to MainViewModel method "RelayCommand GoBack".
private RelayCommand _goBack;
public RelayCommand GoBack
{
get
{
return _goBack
?? (_goBack = new RelayCommand(
() =>
_navigationService.GoBack();
}));
}
}
Why is this button changing view only once? If I want to click it secound time
it doesn't work (nothing happend). If I change page for another by another button its starting work again and againg only for once.
Part of implementation of FrameNavigationService:
public FrameNavigationService()
{
_pagesByKey = new Dictionary<string, Uri>();
_historic = new List<string>();
}
public void GoBack()
{
if (_historic.Count > 1)
{
_historic.RemoveAt(_historic.Count - 1);
NavigateTo(_historic.Last(), null);
}
}
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, null);
}
public virtual void NavigateTo(string pageKey, object parameter)
{
lock (_pagesByKey)
{
if (!_pagesByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
var frame = GetDescendantFromName(Application.Current.MainWindow, "MainFrame") as Frame;
if (frame != null)
{
frame.Source = _pagesByKey[pageKey];
}
Parameter = parameter;
_historic.Add(pageKey);
CurrentPageKey = pageKey;
}
}
What can I do to handle this? May be I should do it tottaly differently?
You should possibly not be doing goback at all.
Unless you really want to use the journal, using a frame and pages is a bad idea. It's a rare requirement to go back to the last view in desktop apps. What with them not being a web browser.
Maybe you have that requirement though.
If you have a frame then you have it's journal and you can just call goback on the frame's navigationservice.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.navigation.navigationservice.goback?view=netframework-4.8
You set keepalive on pages.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.page.keepalive?view=netframework-4.8
You wrote that code and it seems to be largely reproducing navigationservice functionality. From what you've shown us.
As it is.
Use type rather than a magic string as the key. A type is checked at compile time, a magic string is not and you can make mistakes.
Have you explored this issue at all? I think maybe this is one of those times that telling someone what they did wrong isn't really helping as much as telling them how they ought to diagnose.
Debugging is a key skill for any developer.
You have the code running in front of you.
Put break points in, step through and examine what is happening.
When you navigate, what ends up in _historic?
When you goback, what happens exactly?
When you click the goback that second time what path does it go down and what state is causing that.
Make sure you are using RelayCommand in GalaSoft.MvvmLight.CommandWpf,not at GalaSoft.MvvmLight.Command.RelayCommand
Related
Stuck with a problem with WPF. I should mention that I'm very very new to WPF. I'm building small apps for myself to understand the topics.
At the moment I'm stuck updating a listbox calling a class that is in my "_classes" folder which gets information from a remote computer. The reason I put it in a different folder was to avoid all the mess behind the XAML. I can get the GUI freezing issue fixed if I want to put my code behind the XAML which is not ideal from what I've been reading.
The examples given or searched on here or other sites are kind of confusing with no explanations. It would be awesome if someone can actually put comments where I'm stuck and point out what I was doing wrong after they correct it. After all I'm trying to learn this. Going forward, whats the best way to implement these kind of long processing tasks? Create a folder? Call classes? Different solutions? Different projects? etc. I've been reading a lot about this and everyone seems to have their own opinion on this.
Also, I searched this and gotten no where. I feel like I'm going to be the first one to ask this but is MVVM necessary for responsive UI? Can I just implement async/await and be done with it like I'm trying to do in the example I have below?
This is the code I have at the moment. Although I get the results I want, the GUI is unresponsive. I added the thread.sleep there to simulate a long process.
Although I tried different things, this is the latest code I have at the moment.
This is what I had in mind that the app would do:
Click on a button. Listbox displays "getting information"
The process is running in the background (gathering information)
GUI is responsive and I can do other things on the GUI (minimize, change tabs,etc).
Once the process is done, add the info to the Listbox.
Thank you everyone in advance.
PS. Please ignore the naming conventions for now. I've been working on this for a while and just gave up on that part till I actually fix the issue.
XAML
<StackPanel>
<Button Name="test" Height="30" Width="70" Background="red" Content="Submit"
Click="test_Click" />
<ListBox x:Name="listboxResult" />
</StackPanel>
Code Behind XAML
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void test_Click(object sender, RoutedEventArgs e)
{
listboxResult.Items.Clear();
listboxResult.Items.Add("Getting listbox results...");
try
{
await Task.Factory.StartNew(() =>
{
getResults("Passing String Argument", listboxResult);
});
}
catch (Exception)
{
throw;
}
}
private void getResults(string v, ListBox listBoxIn)
{
this.Dispatcher.Invoke((Action)(() =>
{
ReturnListbox _result = new ReturnListbox(v, listBoxIn);
}));
}
}
My class in _classes folder
public class ReturnListbox
{
private ListBox _myListBox;
private string _ComputerName;
public ListBox MyListBox
{
get { return _myListBox; }
set { _myListBox = MyListBox; }
}
public string CName
{
get { return _ComputerNAme; }
set { _ComputerName = CName; }
}
public ReturnListbox(string ComputerName, ListBox IncomingListBox)
{
BuildListBox(ComputerName, IncomingListBox);
}
private void BuildListBox(string CName, ListBox MyListBox)
{
Thread.Sleep(5000);
_myListBox = MyListBox;
MyListBox.Items.Clear();
try
{
ManagementScope Manage = new ManagementScope(string.Format("\\\\{0}\\root\\cimv2", CName));
Manage.Connect();
ObjectGetOptions objectOptions = new ObjectGetOptions();
ManagementPath managementPath = new ManagementPath("Win32_OperatingSystem");
ManagementClass Class = new ManagementClass(Manage, managementPath, objectOptions);
foreach (ManagementObject Object in Class.GetInstances())
{
// Display the remote computer information
MyListBox.Items.Add(string.Format("Computer Name : {0}", Object["csname"]));
MyListBox.Items.Add(string.Format("Windows Directory : {0}", Object["WindowsDirectory"]));
MyListBox.Items.Add(string.Format("Operating System: {0}", Object["Caption"]));
MyListBox.Items.Add(string.Format("Version: {0}", Object["Version"]));
MyListBox.Items.Add(string.Format("Manufacturer : {0}", Object["Manufacturer"]));
}
{
catch (Exception ex)
{
MyListBox.Items.Add(string.Format("Something is going on..."));
}
}
You can read about async programming in WPF here
It would be awesome if someone can actually put comments where I'm stuck
What you are using is not MVVM as you think. You also need get more knowledge about threading
is MVVM necessary for responsive UI? Can I just implement async/await and
be done with it like I'm trying to do in the example I have below?
MVVM is not necessary. You can use async/await. They are not related to each other
Although I get the results I want, the GUI is unresponsive
Your GUI is unresponsive because you are doing your tasks in UI thread. By calling this.Dispatcher.Invoke you are saying that you want code inside to be executed in Dispatcher thread (which is actually responsible for handling UI)
I have this Windows Phone Page where I load data through the standard ViewModel scope.
public Profile()
{
InitializeComponent();
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
This works fine. However when I navigate to this page from some other page the data is still the same. I mean the LoadData() method should recheck updated data right? Please suggest.
EDIT:
My PersonalizedViewModelClass:
public class PersonalizationViewModel: INotifyPropertyChanged
{
public PersonalizationViewModel()
{
this.favorites = new ObservableCollection<ItemViewModel>();
this.Bar = new ObservableCollection<Bars>();
}
public ObservableCollection<ItemViewModel> favorites { get; private set; }
public ObservableCollection<Bars> Bar { get; private set; }
private string _sampleProperty = "Sample Runtime Property Value";
public string SampleProperty
{
get
{
return _sampleProperty;
}
set
{
if (value != _sampleProperty)
{
_sampleProperty = value;
NotifyPropertyChanged("SampleProperty");
}
}
}
public bool IsDataLoaded
{
get;
private set;
}
/// <summary>
/// Creates and adds a few ItemViewModel objects into the Items collection.
/// </summary>
public async void LoadData()
{
favorites.Clear();
try
{
var query = ParseObject.GetQuery("Favorite")
.WhereEqualTo("user", ParseUser.CurrentUser.Username);
IEnumerable<ParseObject> results = await query.FindAsync();
this.favorites.Clear();
foreach (ParseObject result in results)
{
string venue = result.Get<string>("venue");
string address = result.Get<string>("address");
string likes = result.Get<string>("likes");
string price = result.Get<string>("price");
string contact = result.Get<string>("contact");
this.favorites.Add(new ItemViewModel { LineOne=venue, LineTwo=address, LineThree=likes, Rating="", Hours="", Contact=contact, Price=price, Latitude="", Longitude="" });
}
if (favorites.Count == 0)
{
// emailPanorama.DefaultItem = emailPanorama.Items[1];
MessageBox.Show("You do not have any saved cafes. Long press a cafe in main menu to save it.");
}
}
catch (Exception exc)
{
MessageBox.Show("Data could not be fetched!", "Error", MessageBoxButton.OK);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Implementation of PersonalizedViewModel:
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
await App.PersonalizedViewModel.LoadData();
user_tb.Text = ParseUser.CurrentUser.Username;
if (NavigationContext.QueryString.ContainsKey("item"))
{
var index = NavigationContext.QueryString["item"];
var indexParsed = int.Parse(index);
mypivot.SelectedIndex = indexParsed;
}
if (NavigationService.BackStack.Any())
{
var length = NavigationService.BackStack.Count() - 1;
var i = 0;
while (i < length)
{
NavigationService.RemoveBackEntry();
i++;
}
}
}
I don't see the problem, however, I think you need to narrow in on the problem.
First off, you are calling LoadData from 2 places. 1 from MainPage_Load and 1 from OnNavigatedTo. In MainPage_Load it is conditional and in OnNavigatedTo it is always being called. I suggest that you get to a single path through the code instead of 2 so that you don't get different experiences. I personally recommend (without knowing all the details) that you call load data from OnNavigatedTo instead of MainPage_Load. If you want to do it conditionally that is fine but if you are loading the data from memory, it really is unnecessary as you won't improve performance anymore than a few milliseconds. Also, if you are not loading from memory, you may not want to load it conditionally because the underlying data may have changed. In either case, the choice to load data or not should be moved out of the view and into the data layer (but that is for another post).
Once you have a single path chosen (i.e. calling LoadData from MainPage_Load or OnNavigatedTo) you should use your debugger. Put a break point in LoadData method and if it is being called appropriately, then your problem is more specific than your posted question. Here are some questions to think about (you may want to start from the last question and work your way backward)
Questions:
Is LoadData being called appropriately?
Does ParseObject have the correct data?
Is the ParseUser...UserName set properly?
Is the foreach being executed the proper # of times (i.e. does the result of your query have the right # of items?)
Couple Code Tips completely unrelated to this problem:
Single Path through code. Don't call LoadData from more than one place.
Don't call favorites.clear() twice in the same method. (it is called twice in LoadData)
Consistent naming. favorites is lowercase but Bar is upper case.
User proper data types. On your ItemViewModel you have Hours, Latitude, and Longitude. You have them as strings. These clearly are not strings. Also, you should not set them to empty. Empty means they have been set to a value. Emtpy is a valid value. Null means not set. To keep your objects clean and accurate you want to be accurate in how you set things and then deal appropriately with the impact. If you really really want them to be initialized to empty strings, then at least do it in the constructor of ItemViewModel so that every caller doesn't have to know how to initialize every property. I guarantee this is leading to buggy code if you continue using this practice.
Please take the comments as constructive criticism not criticism. I know many people don't like to hear these things but the teams I lead write bugs until they start following these types of guidelines.
Good luck,
Tom
Instead of defining this
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
into constructor i.e. Profile I would suggest remove this code from Constructor and add it into your OnNavigatedTo. so the data will load after navigation
Your OnNavigatedTo Method looks like follows
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
Might be your problem will solve.
Edit
Try this query
var results = (from find in ParseObject.GetQuery("Favorite").WhereEqualTo("user", ParseUser.CurrentUser.Username) select find);
Tried this:
var query = from favorite in ParseObject.GetQuery("Favorite")
where favorite.Get<string>("user") == ParseUser.CurrentUser.Username
select favorite;
IEnumerable<ParseObject> results = await query.FindAsync();
I had a similar Problem.All u want to do here is generate a new instance of the Page.U can do this in two Ways.
One Way is by forcing a GUID along with Page Navigation URI that will create a New Instance of the Page and your Load Data() will work.
NavigationService.Navigate(new Uri(String.Format("/MainPage.xaml?item={0}", Guid.NewGuid().ToString()), UriKind.RelativeOrAbsolute));
The Second Way to implement that Part of your Page in a User Control .Like create a User Control for Load Data() and put it in constructor.It will generate a new Instance everytime you load the Page.
If the problem persists in the front end,you can try this.
1.have you mentioned the below attribute in your xaml page?
<UserControl Loaded="MainPage_Loaded">
So that every time the page loads the data will get loaded on to the page.
2.The data must exist, if you have no problem in the code behind as it is a WPF application and not a web page.
Hope you find it useful.
Two changes required..
Remove the this.Loaded from OnNavigatedTo. That may not be required.
Second move the LoadData to OnNavigatedTo method
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
App.PersonalizedViewModel.favorites.Clear();
DataContext = App.PersonalizedViewModel;
// this.Loaded += new RoutedEventHandler(MainPage_Loaded);
if (!App.PersonalizedViewModel.IsDataLoaded)
{
App.PersonalizedViewModel.LoadData();
}
}
For the purpose of debugging, you can remove the line if (!App.PersonalizedViewModel.IsDataLoaded) and try.
this is the best way I can think of doing this. Could you give me so hints as to whether this is the correct way or if there is a more efficient way of doing it.
My situation is:
Each time the frame is Update()'ed (Like in XNA) I want to check if something has happened.. Like if the timer for that screen has been running for over 2000 milliseconds. But I only want it to fire once, not every time the frame is updated. This would cause a problem:
if(totalMilliseconds > 2000)
{
this.Fader.FadeIn();
}
So I came up with this method that I have implemented in the GameScreen class that looks like this:
public bool RunOnce(string Alias, bool IsTrue)
{
if (!this.Bools.ContainsKey(Alias))
this.Bools.Add(Alias, false);
if (IsTrue && !this.Bools[Alias])
{
this.Bools[Alias] = true;
return true;
}
else
return false;
}
This basically checks if the passed if statement boolean is true, if it is then it fires once and not again unless the Bool["Alias"] is set back to false. I use it like this:
if(this.RunOnce("fadeInStarted", totalMilliseconds > 2000))
{
this.Fader.FadeIn();
}
This will then only run one time and I think is quite easily readable code-wise.
The reason I have posted this is for two reasons.. Firstly because I wanted to show how I have overcome the problem as it may be of some help to others who had the same problem.. And secondly to see if I have missed an obvious way of doing this without creating a manual method for it, or if it could be done more efficiently.
Your method is interesting, I don't see a problem with it, you've essentially created a new programming construct.
I haven't encountered this situation a lot so what I have done in this situation is always start with the untidy approach:
bool _hasFadedIn = false;
.
if(totalMilliseconds > 2000 && !_hasFadedIn)
{
this.Fader.FadeIn();
_hasFadedIn = true;
}
And 90% of the time I leave it like that. I only change things if the class starts growing too big. What I would do then is this:
_faderControl.Update(totalMilliseconds);
Put the logic for fader control into a separate class, so:
class FaderControl
{
bool _hasFadedIn=false;
public void Update(int totalMilliseconds)
{
if (_hasFadedIn)
return;
if (totalMilliseconds <= 2000)
return;
this.Fader.FadeIn();
_hasFadedIn=true;
}
}
It can be modified to make it configurable, like reseting, setting "start", "end", fadein time, or also controlling fadeout too.
Here's how I would approach this problem.
These are your requirements:
You have arbitrary pieces of logic which you want to execute inside of your Update().
The logic in question has a predicate associated with it which determines whether the action is ready to execute.
The action should execute at most once.
The core concept here is "action with an associated predicate," so create a data structure which represents that concept:
public class ConditionalAction
{
public ConditionalAction(Action action, Func<Boolean> predicate)
{
this.Action = action;
this.Predicate = predicate;
}
public Action Action { get; private set; }
public Func<Boolean> Predicate { get; private set; }
}
So now, your example becomes
var action = new ConditionalAction(
() => this.Fader.FadeIn(),
() => totalMilliseconds > 2000);
In your Update() you need something that can execute these conditional actions:
public void Update(GameTime time)
{
// for each conditional action that hasn't run yet:
// check the action's predicate
// if true:
// execute action
// remove action from list of pending actions
}
Because their predicates are probably unrelated, actions don't necessarily run in order. So this isn't a simple queue of actions. It's a list of actions from which actions can be removed in arbitrary order.
I'm going to implement this as a linked list in order to demonstrate the concept, but that's probably not the best way to implement this in production code. Linked lists allocate memory on the managed heap, which is generally something to be avoided in XNA. However, coming up with a better data structure for this purpose is an exercise best left for another day.
private readonly LinkedList<ConditionalAction> pendingConditionalActions =
new LinkedList<ConditionalAction>();
public void Update(GameTime time)
{
for (var current = pendingConditionalActions.First; current != null; current = current.Next)
{
if (current.Value.Predicate())
{
current.Value.Action();
pendingConditionalActions.Remove(current);
}
}
}
public void RegisterConditionalAction(ConditionalAction action)
{
pendingConditionalActions.AddLast(action);
}
Registered actions will wait until their predicates become true, at which point they will be executed and removed from the list of pending actions, ensuring that they only run once.
I m doing unit test for one of my app in WP7. I want to check whether the button_click function works properly. When I try to call the button_click function from my unit testcode
like below
CheckUrVacabolary.MainPage cpage = new CheckUrVacabolary.MainPage();
cpage.txtFind.Text = "home";
cpage.butMeaning_Click(cpage,null);
But the eventhandler(OnDefineInDictCompleted) inside the button_click(OnDefineInDictCompleted)
is not getting called. Here is the code
internal void butMeaning_Click(object sender, RoutedEventArgs e)
{
graphPass.Visibility = Visibility.Collapsed;
graphFail.Visibility = Visibility.Collapsed;
if (txtFind.Text.ToString() != "Enter the word")
{
butNext.IsEnabled = true;
DictServiceSoapClient client = GetDictServiceSoapClient();
String meaningfor;
if (txtRandomWord.Text.Trim().Length != 0)
{
txtRandomWord.Text = "";
meaningfor = wordToGuess;
}
else
{
meaningfor = txtFind.Text.Trim().ToString();
}
if (meaningfor.Length != 0)
{
client.DefineInDictCompleted +=
new EventHandler<DefineInDictCompletedEventArgs
(OnDefineInDictCompleted);
client.DefineInDictAsync("gcide", meaningfor);
}
}
}
I m not using MVVM model in my app. Is there any way I could call the event handler also.
The easy answer would be: do use MVVM nonetheless. One of its benefits is easier unit testing. But if you choose not to, you should make a separate method in a service-like class that is called by the event handler but can also be called from a unit test.
The method should contain only the business logic you want to test and not the UI behaviour. (With a view model you could even test UI behaviour, because it is abstracted). That means it should have arguments for the values you obtain from controls like txtRandomWord.
The event handler that client connects to is a problem.
First, the life cycle of each client instance you create by pressing the button is extended to that of the page, which introduces a potential memory leak.
Second, I assume OnDefineInDictCompleted is a method in your page, so you should extract that from the page as well in a way that it is accessible to unit tests. If that method touches UI elements, this may be a real headache. Again, a strong case for a view model.
I'm making a Silverlight application and I'm using a MediaElement to play a video from the user's disk that I know the path of (say, "C:/foo.MOV"). I'd like a Javascript trigger from the browser to change the source of the MediaElement to another known file (eg "C:/bar.MOV"). I can make a button to do this in Silverlight, and I can have a Javascript trigger execute code inside the Silverlight app, but when I do, the MediaElement appears empty.
I've even tried having the Javascript call the btnLoadNewMediaTest_Click event, and while that event works fine called from user clicks on the button, it doesn't affect the media at all when called from outside the app.
Looking at the MediaElement in the debug, it seems that when it's called from the Javascript the MediaElement's Source appears as null and it seems to have made an empty copy.
I can confirm the Javascript is triggering the events in Silverlight, as it trips breakpoints in the Silverlight code.
I have managed to solve this: I created an EntryPoint class that is scriptable from JavaScript. When the JavaScript sendCommand is triggered, it puts a command and args into a queue held by the entry point. Every tick of a timer in the Silverlight app, the app checks the Count() of the queue and gets any commands and processes them.
From the Javascript, I call silverlightControl.Context.EntryPoint.setCommand("commandname", "args").
In the EntryPoint I have
[ScriptableMember()]
public string setCommand(string commandValue, string argsValue)
{
commands.Enqueue(commandValue);
args.Enqueue(argsValue);
commandWaitingFlag = true;
return Application.Current.HasElevatedPermissions.ToString();
}
In the Silverlight itself, I have a DispatcherTimer with an interval of 100ms. This has a tick event:
public void Each_Tick(object o, EventArgs e)
{
//Other code
if (entryPoint.commandWaitingFlag)
{
handleEntryPointCommands();
}
}
From inside handleEntryPointCommands I call a method of the entryPoint, getCommand():
public string[] getCommand() {
string commandOut = string.Empty;
string argsOut = string.Empty;
if (commands.Count > 0)
{
commandOut = commands.Dequeue();
argsOut = args.Dequeue();
if (commands.Count == 0)
{
commandWaitingFlag = false;
}
return new string[2] { commandOut, argsOut };
}
else
{
return new string[2];
}
}
and then can use the command I've gotten however I like. Hopefully that's more helpful with some code.