Prism NavigationService get previous view name - c#

Currently I'm implementing a Screen indicating wheater a module is not existing or still in development.
The Back Button has the following code:
regionNavigationService.Journal.GoBack();
This is working as expected. But the user is not coming from the Home Screen. So I need to access the View Name from the last Entry in Navigation Journal.
Example: User is coming from Settings Screen => The text should display "Back to Settings Screen"

Assuming the view name you are looking for is when you do new Uri("Main", UriKind.Relative) that you would want the word Main as the view name.
The forward and backward stacks in the RegionNavigationJournal are private. You could use reflection to get access to it.
var journal = regionNavigationService.Journal as RegionNavigationJournal;
if (journal != null)
{
var stack =
(Stack<IRegionNavigationJournalEntry>)
typeof (RegionNavigationJournal).GetField("backStack",
BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(journal);
var name = stack.Peek().Uri.OriginalString;
}
Or a better way is to implement your own IRegionNavigationJournal that is a wrapper around it. This is using Unity to constructor inject the default RegionNavigationJournal if using MEF you might need to put the ImportingConstructorAttribute on it.
public class RegionNavigationJournalWrapper : IRegionNavigationJournal
{
private readonly IRegionNavigationJournal _regionNavigationJournal;
private readonly Stack<Uri> _backStack = new Stack<Uri>();
// Constructor inject prism default RegionNavigationJournal to wrap
public RegionNavigationJournalWrapper(RegionNavigationJournal regionNavigationJournal)
{
_regionNavigationJournal = regionNavigationJournal;
}
public string PreviousViewName
{
get
{
if (_backStack.Count > 0)
{
return _backStack.Peek().OriginalString;
}
return String.Empty;
}
}
public bool CanGoBack
{
get { return _regionNavigationJournal.CanGoBack; }
}
public bool CanGoForward
{
get { return _regionNavigationJournal.CanGoForward; }
}
public void Clear()
{
_backStack.Clear();
_regionNavigationJournal.Clear();
}
public IRegionNavigationJournalEntry CurrentEntry
{
get { return _regionNavigationJournal.CurrentEntry; }
}
public void GoBack()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go back
_regionNavigationJournal.GoBack();
// if currententry isn't equal to previous entry then we moved back
if (CurrentEntry != currentEntry)
{
_backStack.Pop();
}
}
public void GoForward()
{
// Save current entry
var currentEntry = CurrentEntry;
// try and go forward
_regionNavigationJournal.GoForward();
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry != currentEntry)
{
_backStack.Push(currentEntry.Uri);
}
}
public INavigateAsync NavigationTarget
{
get { return _regionNavigationJournal.NavigationTarget; }
set { _regionNavigationJournal.NavigationTarget = value; }
}
public void RecordNavigation(IRegionNavigationJournalEntry entry)
{
var currentEntry = CurrentEntry;
_regionNavigationJournal.RecordNavigation(entry);
// if currententry isn't equal to previous entry then we moved forward
if (currentEntry != null && CurrentEntry == entry)
{
_backStack.Push(currentEntry.Uri);
}
}
}
If using unity in your Prism Bootstrapper you will need to replace the default registration of the IRegionNavigationJournal
protected override void ConfigureContainer()
{
this.RegisterTypeIfMissing(typeof(IRegionNavigationJournal), typeof(RegionNavigationJournalWrapper), false);
base.ConfigureContainer();
}
If using MEF you will need to put the ExportAttribute on top of the RegionNavigationJournalWrapper
[Export(typeof(IRegionNavigationJournal))]
You can see http://msdn.microsoft.com/en-us/library/gg430866%28v=pandp.40%29.aspx for more information on replacing their default implementation with your own. Once you have the wrapper you will still need to cast it as RegionNavigationJournalWrapper to get access to the PreviousViewName so still not perfect or create an interface that RegionNavigationJournalWrapper also implements to cast to that to get you access to the PreviousViewName

Related

C# Command parser

im currently expanding my knowledge a little, and wanted to Create a little game for myself.
The Structure is as Following:
Programm.cs creates an instance of Gamecontroller. This Gamecontroller is the lowest level i want to Access. It will create instaces of the Views, and from classes like config.
I want to implement an debug Console with Command Input. These Commands should always start at the Gamecontroller level, and should be able to interact with kinda everything i could do with C# code.
So i want to access the Objects, Member and methods withing Gamecontroller, or Within any nested object.
Currently i cant get to the Properties of an Child, because _member returns an "Type" which gets parsed to RuntimeProperty instead of the Class
Example on Parsing:
"objPlayer.name" > "GameController.objPlayer.name"
"objConfig.someSetting = 10" > "GameController.objConfig.someSetting=10"
"objConfig.functionCall()" > "GameController.objConfig.functionCall()"
"objConfig.objPlayer.setName("someName")" > "GameController.objConfig.objPlayer.setName("someName")"
"objPlayer.name" > "GameController.objPlayer.name"
this is what i got so far:
private void parseComamnd(string Command)
{
var actions = Command.Split('.');
var start = this.GetType();
var last = actions[actions.Length - 1];
foreach (var action in actions)
{
if (action.Contains("(") && action.Contains(")"))
{
_exec(start, action);
}
else
{
start = _member(start, action);
}
}
}
private Type _member(Type pHandle, string pStrMember)
{
return pHandle.GetProperty(pStrMember).GetType();
}
private void _exec(Type pHandle, string pStrFunction)
{
var Regex = new Regex(#"\(|,|\)");
var FunctionParams = Regex.Split(pStrFunction);
var FunctionName = FunctionParams[0];
FunctionParams[0] = "";
FunctionParams = FunctionParams.Where(val => val != "").ToArray();
pHandle.GetMethod(FunctionName).Invoke(FunctionName, FunctionParams);
}
If I understood right, you want to match some string commands with actions you want to perform. In this case you could use Dictionary as a storage for string-delgate couples to match your string commands to actions you want to perform. As an advantage of this approach, you can change matched couples during program runtime as you wish
class SomeClass
{
delegate void OperationDelegate(string value);
IDictionary<string, OperationDelegate> Operations = new Dictionary<string, OperationDelegate>();
public SomeClass()
{
Operations.Add("objPlayer.name", SetName);
Operations.Add("objConfig.someSetting", SetSetting);
}
public void HandleNewValue(string command, string value)
{
try
{
if (Operations.ContainsKey(command))
Operations[command](value);
}
catch (Exception e)
{
Logger.Error(e);
}
}
private void SetName(string value)
{
// Some logic there
}
private void SetSetting(string value)
{
// Some logic there
}
}

DataGrid sets all items to same value , when i want to change only 1 item

when i try to set a specific Item in dataGrid , it changes all other item's values to that same value. I'm not sure if it's a bug or i done something wrong. Here is my code:
(Datagrid is in another window (Main window), so i called a function in that window to edit the value)
private void AAbutton1_Click(object sender, RoutedEventArgs e)
{
Account selected = new Account();
if (textBox2.Text != null)
selected.username = textBox2.Text;
if (textBox12.Text != null)
selected.password = textBox12.Text;
if (locationTxtBox2.Text != null)
selected.location = locationTxtBox2.Text;
MainWindow.Instance.editAccount(selected);
MainWindow.Instance.updateData();
MainWindow.Instance.needsSave = true;
}
And here is the function in the main window:
public void editAccount(Account acc)
{
Account acc2;
Account selected = (Account)dataGrid.SelectedItem;
acc2 = Manager.accounts.ElementAt(Manager.accounts.FindIndex(a=> a == selected));
acc2.username = acc.username;
acc2.password = acc.password;
acc2.location = acc.location;
}
I really couldn't find a solution for this problem.
And here is the Account class in case you need it:
public class Account
{
public String username { get; set; }
public String password { get; set; }
public String location { get; set; }
public Account(String username,String password, String location)
{
this.username = username;
this.password = password;
this.location = location;
}
public Account()
{
}
}
Just to mention , i use Mahapps.metro controls.
I was right! I read your mind.
This isn't a WPF question, a binding question, or a DataGrid question. It's a "how do references work in C#?" question. It's a good question.
On file load, you start with a list of encrypted Accounts, but in decryption, you copy all the decrypted properties of each one of the accounts into the same instance of Account, and add that one instance multiple times to the list. The decrypted ones are all the same instance. You start off OK, but then you go off the rails in DecryptAccounts().
Here's the bug:
public static void DecryptAccounts()
{
// Hmmm. What's he planning to do with this?
Account holder = new Account(null, null, null);
accounts.Clear();
foreach (Account acc in Encryptedaccounts)
{
// HERE IT IS. This is the same instance of holder on every
// iteration. After file load, every Account in accounts is the
// same object as every other.
// You need to create a new Account object for each account.
holder.username = Decrypt(acc.username, user.Decryptedpassword);
holder.password = Decrypt(acc.password, user.Decryptedpassword);
holder.location = Decrypt(acc.location, user.Decryptedpassword);
accounts.Add(holder);
}
}
public static void LoadFromFile()
{
if (File.Exists(Path.Combine(appdata, folder, file)))
{
Encryptedaccounts = JsonConvert.DeserializeObject<List<Account>>(File.ReadAllText(Path.Combine(appdata, folder, file)));
}
DecryptAccounts();
}
Here's the fix
Manager.cs
public Account DecryptAccount(Account acc)
{
return new Account {
username = Decrypt(acc.username, user.Decryptedpassword),
password = Decrypt(acc.password, user.Decryptedpassword),
location = Decrypt(acc.location, user.Decryptedpassword)
};
}
public static void DecryptAccounts()
{
accounts.Clear();
foreach (Account acc in Encryptedaccounts)
{
accounts.Add(DecryptAccount(acc));
}
}
// You've got the same issue here
private static void EncryptAccounts()
{
Encryptedaccounts.Clear();
foreach (Account acc in accounts)
{
Encryptedaccounts.Add(EncryptAccount(acc));
}
}
public Account EncryptAccount(Account acc)
{
return new Account {
username = Encrypt(acc.username, user.Decryptedpassword),
password = Encrypt(acc.password, user.Decryptedpassword),
location = Encrypt(acc.location, user.Decryptedpassword)
};
}
Some other issues here. Not bugs, but life will be easier if you do stuff the "proper WPF way":
Manager.accounts should be of type ObservableCollection<Account>. Then it will automatically notify the DataGrid whenever you add or remove items from it and you won't have to do this updateData() thing to manually refresh the grid all the time.
Manager and Account both ought to implement INotifyPropertyChanged and fire notifications on their properties when their values change. In C#6, this is very simple:
using System.Runtime.CompilerServices;
using System.ComponentModel;
// ... snip ...
public event PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
Then your properties look like this:
private String _username = null;
public String username {
get { return _username; }
set {
if (value != _username)
{
_username = value;
OnPropertyChanged();
}
}
}
When they do that, anything you bind them to in the UI will be notified whenever you change the values. You'll be able to set properties on the selected grid item and the UI will update without any grid refresh or anything -- it'll just know. Very convenient.

Get All TurnBased Matches Info from GooglePlayServices, Unity Plugin

I'm using the TicTacToss code as a basis for a turn based game on android in Unity, using this : https://github.com/playgameservices/play-games-plugin-for-unity
Everything is working great, but there's no functionality exposed to return a list of games the current user is engaged in, active and completed. I need this for the main UI, so I can show the user what games they are playing, against whom etc. Without, this API seems pretty lacking.
Has anyone been able to get this going? Seems to me that the functionality might be there, as there is a function called GetAllTurnbasedMatches(), but my C# isn't strong enough to understand how it works. (see in https://github.com/playgameservices/play-games-plugin-for-unity/blob/master/source/PluginDev/Assets/GooglePlayGames/Platforms/Native/PInvoke/TurnBasedManager.cs)
(more info # https://github.com/playgameservices/play-games-plugin-for-unity/issues/414)
I managed to get this working back adding the following to NativeTurnBasedMultiplayerClient.cs:
public void GetAllTBMatches(Action<TurnBasedMatch, TurnBasedMatch.MatchTurnStatus> callback)
{
mTurnBasedManager.GetAllTurnbasedMatches(allMatches =>
{
foreach (var match in allMatches.MatchesMyTurn())
{
var converted = match.AsTurnBasedMatch(mNativeClient.GetUserId());
Logger.d("MY turn : Passing converted match to user callback:" + converted);
callback(converted, TurnBasedMatch.MatchTurnStatus.MyTurn);
}
foreach (var match in allMatches.MatchesTheirTurn())
{
var converted = match.AsTurnBasedMatch(mNativeClient.GetUserId());
Logger.d("Their Turn : Passing converted match to user callback:" + converted);
callback(converted, TurnBasedMatch.MatchTurnStatus.TheirTurn);
}
foreach (var match in allMatches.MatchesCompleted())
{
var converted = match.AsTurnBasedMatch(mNativeClient.GetUserId());
Logger.d("Completed : Passing converted match to user callback:" + converted);
callback(converted, TurnBasedMatch.MatchTurnStatus.Complete);
}
callback(null, TurnBasedMatch.MatchTurnStatus.Unknown);
});
}
public void GetAllTBInvitations(Action<Invitation> callback)
{
mTurnBasedManager.GetAllTurnbasedMatches(allMatches =>
{
foreach (var invitation in allMatches.Invitations())
{
var converted = invitation.AsInvitation();
Logger.d("Passing converted invitation to user callback:" + converted);
callback(converted);
}
callback(null);
});
}
You also need to edit the following in TurnBasedManager.cs
internal class TurnBasedMatchesResponse : BaseReferenceHolder {
internal TurnBasedMatchesResponse(IntPtr selfPointer) : base(selfPointer) {
}
protected override void CallDispose(HandleRef selfPointer) {
C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_Dispose(SelfPtr());
}
internal CommonErrorStatus.MultiplayerStatus Status() {
return C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetStatus(SelfPtr());
}
internal IEnumerable<MultiplayerInvitation> Invitations()
{
return PInvokeUtilities.ToEnumerable(
C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetInvitations_Length(SelfPtr()),
index => new MultiplayerInvitation(C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetInvitations_GetElement(SelfPtr(), index)));
}
internal IEnumerable<NativeTurnBasedMatch> MatchesMyTurn()
{
return PInvokeUtilities.ToEnumerable(
C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetMyTurnMatches_Length(SelfPtr()),
index => new NativeTurnBasedMatch(C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetMyTurnMatches_GetElement(SelfPtr(), index)));
}
internal IEnumerable<NativeTurnBasedMatch> MatchesTheirTurn()
{
return PInvokeUtilities.ToEnumerable(
C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetTheirTurnMatches_Length(SelfPtr()),
index => new NativeTurnBasedMatch(C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetTheirTurnMatches_GetElement(SelfPtr(), index)));
}
internal IEnumerable<NativeTurnBasedMatch> MatchesCompleted()
{
return PInvokeUtilities.ToEnumerable(
C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetCompletedMatches_Length(SelfPtr()),
index => new NativeTurnBasedMatch(C.TurnBasedMultiplayerManager_TurnBasedMatchesResponse_GetCompletedMatches_GetElement(SelfPtr(), index)));
}
internal static TurnBasedMatchesResponse FromPointer(IntPtr pointer)
{
if (PInvokeUtilities.IsNull(pointer)) {
return null;
}
return new TurnBasedMatchesResponse(pointer);
}
}

Refactoring similar method using a LinkedList

This is a refactoring question mainly.
I am creating some methods to go back/forward through an actions history depending on its Id/PreviousId relationship (see basic class below):
public class Action
{
public int Id { get; set; }
public int PreviousId { get; set; }
public string Title { get; set; }
}
Background Info:
I start off by getting a single action from the database. If the user selects 'GoBack', I need to get the previous action from the database and store it in a LinkedList. This means users can potentially revisit that same action (i.e. by going back then forward again) but by calling it from the LinkedList version rather than getting it from the database again. I don't want to initially retrieve all actions first either from the database. I have this functionality working but my GoBack() and GoForward() methods are pretty much identical.
I was hoping to see if there is a good way of refactoring this into a more generic method set rather than duplicating code? (Note - my code doesn't include the database calls to reduce reading so instead I've put dummy data into a List to act as my database).
Class level variables I'm referencing in the methods:
//The list I'm using to pretend to be my database containing actions
private List<Action> _actions { get; set; }
private Action _currentAction { get; set; }
private LinkedList<Action> _actionLinks { get; set; }
Here is my GoBack() method:
private void GoBack()
{
var current = _actionLinks.Find(_currentAction);
if (current == null)
return;
//If we've already stored the previous action. Just point to it
if (current.Previous != null)
{
_currentAction = current.Previous.Value;
return;
}
//We don't have this action stored so go get it from the database and cache it in the list
var previousAction = _actions.FirstOrDefault(i => i.Id == _currentAction.PreviousId);
//There are no previous actions
if(previousAction == null)
return;
_actionLinks.AddBefore(current, previousAction);
//Now reset the current action
_currentAction = previousAction;
}
Here is my GoForward() method:
private void GoForward()
{
var current = _actionLinks.Find(_currentAction);
if (current == null)
return;
//If we've already stored the next action. Just point to it
if (current.Next != null)
{
_currentAction = current.Next.Value;
return;
}
//We don't have this action stored so go get it from the database and cache it in the list
var nextAction = _actions.FirstOrDefault(i => i.PreviousId == _currentAction.Id);
//There are no further actions
if (nextAction == null)
return;
_actionLinks.AddAfter(current, nextAction);
//Now reset the current action
_currentAction = nextAction;
}
If you want to compile the code. I've added in my Constructor and BuildData method I'm using to test this:
Constructor:
public LinkListTest()
{
_actionLinks = new LinkedList<Action>();
_actions = new List<Action>();
BuildData();
//Just set current to the latest action id
_currentAction = _actions.First(i => i.Id == 6);
//Add it to the linkedlist
_actionLinks.AddFirst(_currentAction);
//Start navigating as a user would
GoBack();
GoBack();
GoForward();
GoBack();
GoForward();
GoBack();
GoBack();
}
BuildData method:
private void BuildData()
{
for (int i = 6; i >= 0; i--)
{
var action = new Action();
action.Id = i;
if (i != 0)
action.PreviousId = i - 1;
else
action.PreviousId = -1;
action.Title = string.Format("Action {0}", i);
_actions.Add(action);
}
}
Thanks in advance!
One way to de-duplicate some of the logic here is to use the visitor pattern.
using ActionListAction = System.Action<System.Collections.Generic.LinkedList<Package.Action>, System.Collections.Generic.LinkedListNode<Package.Action> ,Package.Action>;
...
private void GoBack()
{
Move(new BackwordVisitor());
}
private void GoForward()
{
Move(new ForwardVisitor());
}
private void Move(DirectionVisitor direction)
{
var current = _actionLinks.Find(_currentAction);
if (current == null)
return;
var node = direction.Pointer(current);
if (node != null)
{
_currentAction = node.Value;
return;
}
var action = _actions.FirstOrDefault(i => direction.NextSelector(i, _currentAction));
//There are no further actions
if (action == null)
return;
direction.Add(_actionLinks, current, action);
_currentAction = action;
}
private abstract class DirectionVisitor
{
public Func<LinkedListNode<Action>, LinkedListNode<Action>> Pointer { protected set; get; }
public Func<Action, Action, bool> NextSelector { protected set; get; }
public ActionListAction Add { protected set; get; }
}
private class ForwardVisitor : DirectionVisitor
{
public Forward()
{
Pointer = n => n.Next;
NextSelector = (action, current) => action.PreviousId == current.Id;
Add = (list, current, node) => list.AddAfter(current, node);
}
}
private class BackwordVisitor : DirectionVisitor
{
public Backword()
{
Pointer = n => n.Previous;
NextSelector = (action, current) => action.Id == current.PreviousId;
Add = (list, current, node) => list.AddBefore(current, node);
}
}
Since there are only two options for moving through the list, this may be overkill for this particular scenario. Passing an enum into the Move method with the direction and using conditionals may read better.

C# SSIS Custom Control Flow Task List Variables in Property Grid Drop Down List

I'm putting together a custom control flow SSIS task for the first time in C#. On my task UI editor I have a property grid and in one of the options I would like to be able to populate a drop down list of any task variables available as well as give the user the option of creating a new one. I've been researching for a few days and I have found some good examples on the forum but I'm a little lost now. My code as follows compiles and the editor displays a drop down list but its blank. After stepping through it, it appears to be down to this line:
taskHostProperty = context.Instance.GetType().GetProperty("TransferTask", typeof(TaskHost));The "TransferTask" being the name of my control flow task. I'm wondering if this is correct?
My full code for this is below.
//Property Grid Property
[Category("General"),
Description("Specifies the local Path for this task"),
Browsable(true),
ReadOnly(false),
DesignOnly(false),
TypeConverter(typeof(VariableConverter)),
DisplayName("Local Path")]
public string LocalPath
{
get
{
return this.stLocalPath;
}
set
{
dtsVariableService = serviceProvider.GetService(typeof(IDtsVariableService)) as IDtsVariableService;
dtsVariableService.PromptAndCreateVariable(parentWindow, dtsContainer,"Local Path","User",typeof(string));
this.stLocalPath = value;
}
}
//Variable Converter
internal class VariableConverter : TypeConverter
{
StandardValuesCollection svc = new StandardValuesCollection(new ArrayList());
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
TaskHost taskHost = null;
PropertyInfo taskHostProperty = null;
List<string> values = new List<string>();
values.Add(NEW_VARIABLE);
if (context == null)
{
return svc;
}
if (context.Instance == null)
{
return svc;
}
taskHostProperty = context.Instance.GetType().GetProperty("TransferTask", typeof(TaskHost));
if (taskHostProperty == null)
{
return svc;
}
taskHost = taskHostProperty.GetValue(context.Instance, null) as TaskHost;
foreach(Variable v in taskHost.Variables)
{
if (!v.SystemVariable && v.DataType == TypeCode.String)
{
values.Add(v.QualifiedName);
}
}
values.Sort();
return new StandardValuesCollection(values);
}
In the end I gave up trying to add the variables to a property grid and just created my own form. I used the following code to populate a combobox and after allowing the user to add a new SSIS variable. I refreshed the datasource. I would still like to know how to do this properly but I just didnt have the time to sit and work it out.
I used the following to first get the SSIS variables I needed, I'm able to add a new SSIS variable and I'm able to select the newly created one. I'm sure there is a better way of doing this but this works for now.
public ObservableCollection<string> FillVariableList()
{
ObservableCollection<string> variables = new ObservableCollection<string>();
variables.Add(string.Empty);
variables.Add(New_Variable);
foreach (Variable v in thetaskHost.Variables)
{
if (!v.SystemVariable && v.DataType == TypeCode.String && !variables.Contains(v.Name))
{
variables.Add(v.Name);
}
}
return variables;
}
And I used the following to allow the user to add a new SSIS variable or select an existing SSIS variable and to refresh the SSIS variable combobox.
private void cmbxVariables_SelectionChangeCommitted(object sender, EventArgs e)
{
if (cmbxVariables.Text == New_Variable)
{
try
{
DtsContainer dtsContainer = null;
dtsVariableServie = serviceProvider.GetService(typeof(IDtsVariableService)) as IDtsVariableService;
Variable var = dtsVariableServie.PromptAndCreateVariable(null, dtsContainer, "VariableName", "User", typeof(string));
if (!var.IsNull())
{
cmbxVariables.DataSource = null;
cmbxVariables.DataSource = FillVariableList();
}
}
catch (Exception exe)
{
MessageBox.Show(exe.ToString());
}
}
else
{
foreach (Variable v in thetaskHost.Variables)
{
if (v.Name == cmbxVariables.Text)
{
//Do something with the variable selected
break;
}
}
}
}

Categories