I am using the Reflection API for MonoTouch.Dialog. What I want to accomplish is, when the user selects an item from a list, I want the navigation controller to go back. I don't want to force the user to click an item, then click the Back button to go back.
However, when trying to use the OnTap attribute, my method doesn't get executed.
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
var demo = new DemoClass();
var context = new BindingContext(this, demo, "Some Demo");
var controller = new DialogViewController(context.Root);
var navController = new UINavigationController(controller);
window.RootViewController = navController;
window.MakeKeyAndVisible ();
return true;
}
public void EnumSelected()
{
InvokeOnMainThread(() =>
{
new UIAlertView("Dialog", "Enum Selected", null, "OK", null).Show();
});
}
DemoClass
public enum DemoEnum
{
SomeValue,
AnotherValue,
YetAnotherValue
}
public class DemoClass
{
[OnTap("EnumSelected")]
public DemoEnum SomeEnum;
}
I know how to navigate back with the navigation controller, but without the OnTap working, I can't get that far. Am I missing something? Can anybody see where I am going wrong?
In a word, you can't.
Enum's (which results in a new RootController and a bunch of RadioElement's) can't have an OnTap set, unless you do it all by hand.
https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Reflect.cs#L337
especially, these bits:
csection.Add (new RadioElement (ca != null ? ca.Caption : MakeCaption (fi.Name)));
element = new RootElement (caption, new RadioGroup (null, selected)) { csection };
There is no trigger added to the RadioElement. You would need to change it to auto-pop the form - which needs a new/changed RadioElement
https://gist.github.com/3569920
(I can't claim this code - it came from #escoz: https://github.com/escoz/MonoMobile.Forms )
So, if you are using the built-in MT.D, you can't do it. If you don't mind maintaining your own branch (or, submit a pull request back, which is what I need to do for a few things), then this is a fairly good way to go.
Related
As part of the app i'm developing i want the ability to change between using the front or back camera, but from my searches and attempts i haven't been able to get it to work using the front camera.
The scanner view doing the scanning is the one from ZXing.Net.Mobile.Forms called ZXingScannerView, defined in my xaml like so, together with the button that should do the flipping of the camera.
<elements:AdvancedTabbedPage
...
xmlns:elements="clr-namespace:Wolf.Utility.Main.Xamarin.Elements;assembly=Wolf.Utility.Main"
xmlns:forms="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms">
...
<ContentPage>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{x:Static resources:AppResources.CameraFlipText}" x:Name="CameraFlipButton" Clicked="CameraFlipButton_OnClicked"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
...
<forms:ZXingScannerView x:Name="ScannerView" HeightRequest="200" IsAnalyzing="False" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" IsVisible="False" IsScanning="True"/>
...
</ContentPage.Content>
</ContentPage>
The Button can be seen in the top right of the following image, while the Scanner View is only visible while scanning is On, which it is not on the image.
Image of the Page where Scannning is happening
Clicking the button should toggle between using the front and back camera, with the front one as the default. Clicking the button however, doesn't seem to do anything, other then write to my Log. Code for the Clicked event of the button can be seen below.
...
private void CameraFlipButton_OnClicked(object sender, EventArgs e)
{
Logging.Log(LogType.Information, "Flipping Camera...");
Config.DefaultOptions.UseFrontCameraIfAvailable = !Config.DefaultOptions.UseFrontCameraIfAvailable;
Config.CustomOptions.UseFrontCameraIfAvailable = !Config.CustomOptions.UseFrontCameraIfAvailable;
if (!ScanningToggle.IsToggled) return;
Logging.Log(LogType.Information, "Restarting Scanning...");
ScanningToggle.IsToggled = false;
ScanningToggle.IsToggled = true;
}
The options mentioned in the above code is defined as so, in my Config class. Additional values in the one called CustomOptions are set in my Init method of my Config class, but those are irrelevant to this question.
public class Config
{
...
public static MobileBarcodeScanningOptions CustomOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
public static MobileBarcodeScanningOptions DefaultOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
...
}
The options that my scanner will use, is always picked between these two, depending on a few user inputs in the settings.
Attempting to get it to work i have also tried to...
Invert the value UseFrontCameraIfAvailable, while scanning is running
Invert the value UseFrontCameraIfAvailable on the options used to start the scan with and then restarting the scan - The code shown above.
Change IsScanning of the ZXingScannerView from true to false, while restarting the scanning with changed options, but this just resulted in the camera freezing.
Found this one as i was about to submit the question. I'm going to attempt to follow that one tomorrow, but would still very much like input on mine.
Fell free to ask questions, or ask for additional code if i have left something out, that you think could help.
I managed to figure out how to successfully flip the camera.
To do so i first remove the ZXingScannerView from my stack that contains it.
Then i create a new instance of the ZXingScannerView, copying over all the settings from the old one (layout positioning, ZXingScannerView specific values and so on).
Then i re-add the ZXingScannerView to the stack, and from there any changes to the UseFrontCameraIfAvailable property took effect.
The code it made to succeed, is as follows. First the generic method that copies over properties, then the method that recreates the ZXingScannerView, and lastly my method that enables the scanning.
public class GenericFactory
{
// Assistance with Setter Accessibility: https://stackoverflow.com/questions/3762456/how-to-check-if-property-setter-is-public
public static T CopyProperties<T>(T newObject, T oldObject, bool ignoreDefaults = true,
bool skipSelectedProperties = true, params string[] skippedProperties) where T : class
{
var type = typeof(T);
var properties = type.GetProperties();
foreach (var property in properties)
{
if (ignoreDefaults && property.GetValue(oldObject) == default)
continue;
if (skipSelectedProperties && skippedProperties.Contains(property.Name))
continue;
if (!property.CanWrite)
continue;
property.SetValue(newObject, property.GetValue(oldObject));
}
return newObject;
}
}
private void RecreateScannerView()
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nam1eof(RecreateScannerView)} method called");
ScannerStack.Children.Remove(ScannerView);
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"Coping properties from existing {nameof(ZXingScannerView)} into a new {nameof(ZXingScannerView)}");
ScannerView = GenericFactory.CopyProperties(new ZXingScannerView() {IsScanning = false}, ScannerView,
skippedProperties: new List<string>() {nameof(ScannerView.IsScanning)}.ToArray());
ScannerView.OnScanResult += ScannerView_OnScanResult;
ScannerStack.Children.Add(ScannerView);
}
private void EnableScan(MobileBarcodeScanningOptions imputedOptions = null)
{
if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nameof(EnableScan)} Method is run in Thread named => {Thread.CurrentThread.Name}");
var chosenOptions = imputedOptions ?? (Config.UseCustomOptions ? Config.CustomOptions : Config.DefaultOptions);
if (Config.DebugMode)
Logging.Log(LogType.Information,
$"Chose this option for Scanning => {(imputedOptions != null ? nameof(imputedOptions) : (Config.UseCustomOptions ? nameof(Config.CustomOptions) : nameof(Config.DefaultOptions)))}");
ScannerView.Options = chosenOptions;
RecreateScannerView();
Logging.Log(LogType.Information, $"Starting the Scanning...");
ScannerView.IsScanning = true;
ScannerView.IsAnalyzing = true;
ScannerView.IsVisible = true;
if (Config.DebugMode)
Logging.Log(LogType.Debug,
$"{nameof(EnableScan)} Called and Finished; ScannerView.IsAnalyzing => {ScannerView.IsAnalyzing}; ScannerView.IsVisible => {ScannerView.IsVisible}");
}
My method to flip the value of UseFrontCameraIfAvailable is the one shown in the question above.
Hope to this ends up helping others who might stumble upon a likewise issue.
I don't think it can switch the front and back cameras when it has started scanning with Zxing,so the option has to be chosen and set beforehand
var options = new MobileBarcodeScanningOptions
{
AutoRotate = true,
UseNativeScanning = true,
TryHarder = true,
TryInverted = true,
UseFrontCameraIfAvailable = true
};
var scannedCode = await _scanner.Scan(options);
The third or so page in my app contains a ListView, but the list for some reason doesn't display until I either toggle the view (which switches the views ItemSource to another list) or rotate the screen.
If I do the toggle twice (so back to the original starting state) the listview is there still. It seems like a bug but I haven't been able to find anything on it.
public partial class ReviewRequestsPage : ContentPage
{
private readonly List<RequestCell> closedRequestCells = new List<RequestCell>();
private readonly List<RequestCell> openRequestCells = new List<RequestCell>();
public ReviewRequestsPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
BindingContext = new SvgImagesViewModels();
new Footer().SetGestureRecognizers(null, Notifications, Help, Home, this);
LoadRequestLists();
ToggleSwitch.PropertyChanged += (o, ea) => { HandleToggle(((Switch) o).IsToggled); };
}
....
private void LoadRequestLists()
{
UserDialogs.Instance.ShowLoading("Loading Requests...", MaskType.Black);
var client = new RestService().Client;
var request =
new RequestService().GetAllRequests();
client.ExecuteAsync(request, response =>
{
var myList = JsonConvert.DeserializeObject<List<Request>>(response.Content, new DateTimeConverter());
myList.ForEach(r =>
{
if (r.status.type == StatusType.CLOSED) closedRequestCells.Add(new RequestCell(r));
else if (r.status.type != StatusType.DELETED) openRequestCells.Add(new RequestCell(r));
});
UserDialogs.Instance.HideLoading();
RequestsList.ItemSource = openRequestCells;
});
}
private void HandleToggle(bool isToggled)
{
Switchlabel.Text = isToggled ? Constants.Closed : Constants.Open;
RequestsList.ItemsSource = isToggled ? closedRequestCells : openRequestCells;
}
Is there something else I should be calling or doing so that the listview appears once I set the ItemSource? It doesn't make sense why it wouldn't be already though. Also nothing is failing and everything is working as expected, other than that
The constructor does not set ItemsSource, at least not immediately. It calls LoadRequestLists that starts an async Task which will eventually set ItemsSource, so at some point in the future, ItemsSource will be set (whenever the Rest response is received and the UI thread happens to run).
Since constructors cannot await an async Task, you will need to refactor your code so that the Rest client runs (and finishes) before the constructor, and so the ReviewRequestsPage will take in the List as a parameter. Then the constructor can build the openRequestCells and closedRequestCells and assign to ItemsSource.
I am using storyboards with Xamarin.iOS. The DashboardViewController (a UINavigationController) is set as the initial view in the storyboard. However, in my AppDelegate class, the FinishedLaunching method is conditionally checking if the user needs to login or not when the app starts up. If so, it will set the "ViewController" (login controller) as the RootViewController, or else it will instantiate the initial view controller as set by the storyboard, which is the "DashboardViewController". Here is the code for the FinishedLaunching method.
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
// Override point for customization after application launch.
// If not required for your application you can safely delete this method
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
bool isLoggedOut = true;
UIViewController rootViewController;
if (isLoggedOut)
rootViewController = (UIViewController)storyboard.InstantiateViewController("ViewController");
else
rootViewController = (UIViewController)storyboard.InstantiateInitialViewController();
Window.RootViewController = rootViewController;
Window.MakeKeyAndVisible();
}
Once the user's credentials are verified, the following code attempts to set the RootViewController again back to the original "DashboardViewController" and present that view. This is done from the login controller.
partial void LoginBtn_TouchUpInside (UIButton sender)
{
var appDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;
appDelegate.SetRootViewController (dashboardViewController, true);
this.PresentViewController (dashboardViewController, true, null);
}
Here is the code for the "SetRootViewController" in the AppDelegate:
public void SetRootViewController()
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
bool isLoggedOut = false;
UIViewController rootViewController;
if (isLoggedOut)
rootViewController = (UIViewController)storyboard.InstantiateViewController ("ViewController");
else
rootViewController = (UIViewController)storyboard.InstantiateViewController (viewController);
Window.RootViewController = rootViewController;
Window.MakeKeyAndVisible();
}
This works so far, but if I try to add another view to the stack in the DashboardViewController (which is a UINavigationController), saying from a button click using the following code,
partial void ProfileBtn_TouchUpInside (UIButton sender)
{
this.NavigationController.PushViewController (profileViewController, true);
}
the NavigationController is null and the app crashes. So the basic flow for this scenario is "Set RootViewController to "ViewController" so the user can log in --> When the user logs in set the RootViewController back to the DashboardViewController and present that view --> Click a button to navigate to another view from DashboardViewController view but the NavigationController in DashboardViewController is null.
What am I doing wrong?? Any help or advice would be appreciated (and I apologize for the long question).
I've decided to leave this question up for those that may come upon the same problem. I've figured out what I believe to be a solution. In my AppDelegate "SetRootViewController" method, instead of saying,
Window.RootViewController = rootViewController;
I needed to be saying,
Window.RootViewController = new UINavigationController(rootViewController);
with everything else staying the same. The workflow now works as expected. This is because the controller I am trying to set as the RootViewController is not just a UIViewController, it's a UIViewController wrapped by a UINavigationController. This answer is also reiterated here in the following link
https://forums.xamarin.com/discussion/23424/how-can-i-use-this-navigationcontroller-on-my-view
I apologize for answering my own question, if anybody has a better method or sees something wrong with this please let me know. Hope this helps someone in the future.
This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);
I have amended the TODO list app to use a badge element instead of the boolean element as follows:
protected void PopulateTable()
{
tasks = TaskManager.GetTasks().ToList ();
UIImage ticked = new UIImage("checkbox_checked.png");
UIImage unticked = UIImage.FromFile("checkbox_unchecked.png");
Root = new RootElement("Tasky") {
new Section() {
from t in tasks
select (Element) new BadgeElement(t.Completed ? ticked : unticked, (t.Name==""?"<new task>":t.Name), delegate {
Console.WriteLine("???");
})
}
};
}
Is it possible to check to see if the user has clicked an icon rather than the text, and change the behaviour? Essentially I want to do this...
var task = tasks[indexPath.Row];
if(clickedIcon) {
currentTask = task;
task.Completed = !task.Completed;
TaskManager.SaveTask(currentTask);
} else {
ShowTaskDetails(task);
}
But I don't see any parameters inside IndexPath that allow me to access the column or the tapped element.
Any ideas
You need to create a custom version of the BadgeElement, and basically raise an event for the image that is separate from raising an event for the text.
Luckily for you, the whole source code is available, so you can just copy/paste BadgeElement, rename it, create a new unique key and modify it.