In my Xamarin Studio project on Mac, I am using MvvmCross version 3.0.13 from MvvmCross-Binaries, the XS-iOS-Mac Release assemblies, and I am trying to couple my CrossUI Dialog based View with a corresponding ViewModel. Specifically, I define the Root in my dialog view like this:
var bindings = this.CreateInlineBindingTarget<ViewModel>();
Root = new RootElement("New Connection") {
new Section {
new StringElement("Test")
.Bind(bindings, element => (object)element.SelectedCommand, vm => vm.TestConnection)
},
new Section {
new StringElement ("Add")
.Bind (bindings, element => element.Visible, vm => vm.CanAddConnection)
.Bind (bindings, element => (object)element.SelectedCommand, vm => vm.AddConnection)
}
};
In the ViewModel, CanAddConnection is set to true by the TestConnection command if test is successful.
When I run this (in the iOS Simulator) and open the dialog, the Test button is displayed and the Add button is hidden (as intended). When I click the button and the test is successful, the Add button is however not displayed, but instead I get this message in the application output:
How did this happen - CurrentAttachedCell is a child of a non-UITableView
Why is my Visible binding not working?
As far as I can tell, I have not made any code customizations upstream that would lead to this failure in the code (but I might be missing something).
If I bind CanAddConnection to another element property, for example Caption, the boolean value is properly updated in the view.
I think you are probably falling foul of an ios7 change which is addressed as part of https://github.com/MvvmCross/MvvmCross/issues/467
This fix will be included in 3.0.14 (hopefully in the next week) - in the meantime, the easiest workaround is probably to patch UpdateVisibility yourself in your own build - or to implement a custom StringElement
Related
We have an EPiServer (forms) edit view with a number of tabs. The customer has requested that the tab which is #2 (called "alternative content"), should be automatically selected under certain conditions when the editors open the edit-page. We don't want to reorder the tabs. How can this be accomplished? We're running EPiServer 11.
Update:
I've tried to accomplish this using a Dojo-script. The result in EPiServer depends on how I set it up, which is kinda strange to me in this case. Here's the file
Web\modules\CMSDefaultTabSelector\module.config:
Here's how it's called in ModifyMetadata:
...and the script itself, CMSDefaultTabSelector.js, looks like this:
The result, depending on whether you send in EditLayoutContainer, Dialog, or skip it entirely (the way it should look), is as follows:
Result with EditLayoutContainer
Result with Dialog
The result as it should be
If I try to skip this parameter, I get the error "ctor is not a constructor". Other layout elements just render an empty tab, weirder errors, or you get other error messages. If I use require([...]), the function is called on every page reload, which is not what I want. I want it to only be called whenever the code from ModifyMetadata kicks in. Hope someone can help.
After fiddling around for quite a while, I finally found the magic code. Since other people might also wonder how this is done, here's how it was solved in the end (using Dojo):
define([
"dojo/_base/declare",
"epi/shell/layout/SimpleContainer"
],
function (
declare,
SimpleContainer
)
{
return declare([SimpleContainer], {
//constructor: function () {},
postCreate: function () { /* PostCreate fires too soon, and the tab strip is not completely rendered */ },
startup: function () {
// Use Jquery to select the tab we manually want to change to, and click it:
var tabElement = $("div.dijitContentPane span.tabLabel:contains('Additional content')");
if ($(tabElement).length) {
$(tabElement).trigger("click");
}
}
});
}
);
I am working on a Xamarin.Forms app, where I was using a FormsApplicationActivity as my main activity and was able to customize the ActionBar with a custom view inside it (I put a Spinner in it, for some page)
But since there was a few UI / look and feel issues I upgraded to FormsAppCompatActivity.
Since I did that I just CAN'T get my spinner in the toolbar / actionbar! No matter what I try!
This was basically the previous code, wroking with FormsApplicationActivity
var activity = (Activity)this.Context;
var bar = activityActionBar;
var dlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
bar.CustomView = new Android.Widget.Button(activity) {
Text = "Click",
LayoutParameters = dlp,
};
bar.DisplayOptions = ActionBarDisplayOptions.ShowCustom;
What should I write to support FormsAppCompatActivity please?
When using FormsAppCompatActivity the NavigationRenderer on android creates a new toolbar internally. It is a private field so far I can see and cannot be accessed.
here is the code : https://github.com/xamarin/Xamarin.Forms/blob/d1a8477233b28e6a20c6f5d4a75128ec2a05e6dc/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs
See image for the specific code part. I am also trying now to get access to view. So just a note the action bar you are trying to edit is the wrong one. That one is created on activity startup.
UPDATE: maybe found a solution look here : https://forums.xamarin.com/discussion/69923/access-to-the-formsappcompatactivity-bar
I'm using WPF MVVM trying to figure out what would be the best way to reload my ViewModel (entire View would work as well I suppose).
The data behind my Model is parsed out of a series of flat files stored within a directory. The location of the directory is saved in the .Settings file and can be the user via a popup window.
If the user changes updates the directory they want to use, how can I recreate my ViewModel so that the data being used is what is in the new directory?
I guess it would be akin to changing to a different database while the app is running if that is what I had as a datastore. Currently I show a message asking the user to restart the application.
Here is the important code:
public SignalViewModel()
{
_trafficSignals = new ObservableCollection<TrafficSignal>(DataAccess.TrafficSignalRepository.GetTrafficSignals());
}
public static List<TrafficSignal> GetTrafficSignals()
{
string dataStore = Properties.Settings.Default.SaveLocation;
var signals = new List<TrafficSignal>();
if (Directory.Exists(dataStore))
{
var files = Directory.GetFiles(dataStore, "CP*.SAV");
Array.Sort(files);
foreach (var file in files)
{
signals.Add(LoadFile(file));
}
}
return signals;
}
I would solve this problem with two events:
Implement the INotifyPropertyChanged in the settings.
In the TrafficSignalRepository I would then implement an event (e.g. SaveLocationChanged) which you raise after the PropertyChanged event of the settings was raised for the SaveLocation-Property
Then you can register for the SaveLocationChanged event inside of the ViewModel. In the registered event handler you just call GetTrafficSignals() again, assign the new value to the field and raise the NotifyPropertyChanged event of the ViewModel. The rest should be done for you automatically by data binding.
You can reload the saved settings using
Properties.Settings.Default.Reload();
And then call GetTrafficSignals() again?
Although having said that, I have had some issues in using the Reload method in the past...
I am working from the sample project here: http://www.codeproject.com/Articles/8086/Extending-the-save-file-dialog-class-in-NET
I have hidden the address/location bar at the top and made other modifications but I can't for the life of me manage to disable the button that lets you go up to the parent folder. Ist is in the ToolbarWindow32 class which is the problem. This is what I have at the moment but it is not working:
int parentFolderWindow = GetDlgItem(parent, 0x440);
//Doesn't work
//ShowWindow((IntPtr)parentFolderWindow, SW_HIDE);
//40961 gathered from Spy++ watching messages when clicking on the control
// doesn't work
//SendMessage(parentFolderWindow, TB_ENABLEBUTTON, 40961, 0);
// doesn't work
//SendMessage(parentFolderWindow, TB_SETSTATE, 40961, 0);
//Comes back as '{static}', am I working with the wrong control maybe?
GetClassName((IntPtr)parentFolderWindow, lpClassName, (int)nLength);
Alternatively, if they do use the parent folder button and go where I don't want them to, I'm able to look at the new directory they land in, is there a way I can force the navigation to go back?
Edit: Added screenshot
//Comes back as '{static}', am I working with the wrong control maybe?
You know you are using the wrong control, you expected to see "ToolbarWindow32" back. A very significant problem, a common one for Codeproject.com code, is that this code cannot work anymore as posted. Windows has changed too much since 2004. Vista was the first version since then that added a completely new set of shell dialogs, they are based on IFileDialog. Much improved over its predecessor, in particular customizing the dialog is a lot cleaner through the IFileDialogCustomize interface. Not actually what you want to do, and customizations do not include tinkering with the navigation bar.
The IFileDialogEvents interface delivers events, the one you are looking for is the OnFolderChanging event. Designed to stop the user from navigating away from the current folder, the thing you really want to do.
While this looks good on paper, I should caution you about actually trying to use these interfaces. A common problem with anything related to the Windows shell is that they only made it easy to use from C++. The COM interfaces are the "unfriendly" kind, interfaces based on IUnknown without a type library you can use the easily add a reference to your C# or VB.NET project. Microsoft published the "Vista bridge" to make these interfaces usable from C# as well, it looks like this. Yes, yuck. Double yuck when you discover you have to do this twice, this only works on later Windows versions and there's a strong hint that you are trying to do this on XP (judging from the control ID you found).
This is simply not something you want to have to support. Since the alternative is so simple, use the supported .NET FileOk event instead. A Winforms example:
private void SaveButton_Click(object sender, EventArgs e) {
string requiredDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var dlg = new SaveFileDialog()) {
dlg.InitialDirectory = requiredDir;
dlg.FileOk += (s, cea) => {
string selectedDir = System.IO.Path.GetDirectoryName(dlg.FileName);
if (string.Compare(requiredDir, selectedDir, StringComparison.OrdinalIgnoreCase) != 0) {
string msg = string.Format("Sorry, you cannot save to this directory.\r\nPlease select '{0}' instead", requiredDir);
MessageBox.Show(msg, "Invalid folder selection");
cea.Cancel = true;
}
};
if (dlg.ShowDialog() == DialogResult.OK) {
// etc...
}
}
}
I don't this is going to work. Even if you disable the button they can type ..\ and click save and it will take them up one level. You can't exactly disable the file name text box and maintain the functionality of the dialog.
You'd be better off either using the FolderBrowserDialog and setting it's RootFolder property and asking the user to type the filename in or auto generating it.
If the folder you are wanting to restrict the users to isn't an Environment.SpecialFolder Then you'll need to do some work to make the call to SHBrowseForFolder Manually using ILCreateFromPath to get a PIDLIST_ABSOLUTE for your path to pass to the BROWSEINFO.pidlRoot
You can reflect FolderBrowserDialog.RunDialog to see how to make that call.
Since you want such custom behaviors instead of developing low level code (that is likely yo break in the next versions of windows) you can try to develop your file picker form.
Basically it is a simple treeview + list view. Microsoft has a walk-through .
It will take you half a day but once you have your custom form you can define all behaviors you need without tricks and limits.
I am into my sixth hour battling with what hopefully should have a simple solution, so I thought I would post here.
I have an feature with a feature receiver whose sole purpose is to activate a deployed list definition feature and then create an instance of that new list definition.
The list definition feature, called "Custom Access List", is scoped at web.
So my feature receiver activates this list definition feature, having GUID "1E503BDA-803B-4a1a-A042-019FA1A70C4C":
...
string featureGuid = "1E503BDA-803B-4a1a-A042-019FA1A70C4C"; // my 'Custom try
{
SPFeatureCollection featureCollection = web.Features;
featureCollection.Add(new Guid(featureGUID), true); // activat the 'Custom Access List' feature
}
catch (Exception e)
{
// log exception
}
This code executes fine, and the list definition feature is activated, and the new list definition appears in the "Create" site menu option in the UI.
However, this is where my issue starts. The next line of my feature receiver code then tries to create an instance of this newly-available list:
SPListTemplate listTemplate = web.ListTemplates["Custom Access List"]; // exception! Value does not fall within the expected range
web.Lists.Add("My new custom access list","", listTemplate);
But the line SPListTemplate listTemplate = web.ListTemplates["Custom Access List"]; throws an exception with "Value does not fall within the expected range." - the list template, despite being deployed, visible and available in the UI under the "Create" site menu action, cannot be found in the receiver code.
Debugging the code confirms that the web.ListTemplates SPListTemplateCollection does not contain a entry for this new "Custom Access List", despite the UI suggesting otherwise.
And here is the weird thing. An exception is thrown, but if I then re-run the code i.e. reactivate the feature in the UI, to re-execute that feature receiver, the list template is then found -
SPListTemplate listTemplate = web.ListTemplates["Custom Access List"]; // found this time. It sees it the second time around
web.Lists.Add("My new custom access list","", listTemplate); // works fine
So, in a nutshell - initially, after activating a feature which, through receiver code, activates a list definition feature, that list definition is not visible until after a "postback" or some form of "SPWeb refresh". Then it is visible.
Am I missing something here? A call of web.Update() here:
try
{
SPFeatureCollection featureCollection = web.Features;
featureCollection.Add(new Guid(featureGUID), true); // true to force activation
web.Update();
}
...
does nothing. Is there some way I can "refresh" the SPWeb object so that the new list template can be seen and used?
The workaround I have found, for now, is to add the "Custom Access List" list template feature as an activation dependency in the "parent" feature receiver itself, and to make the "Custom Access List" list template feature hidden. That way, to my knowledge, the custom list definition feature is forcibly activated and I find that web.ListTemplates["Custom Access List"]; is found.
But I would much rather the former approach work - to activate, in my receiver code, the list definition feature and then to find it so that an instance of the list can then be created.
Andrew,
The problem is to do with internal async events and the timing of the activity. As you say if you go away and come back it works - i.e. the async event has completed. You are treating the featureCollection.Add as a synchronus method.
What you really should be doing if you need a template and a list instance created at the same time is using the XML framework for this.
Add a to your feature that has the list template, or alternatively add a new feature for the list instance and reference the FeatureID of the list template.
Andrew
You need to call EnsureListsData on the SPListCollection that you just updated.
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.splistcollection.ensurelistsdata.aspx
Seems that the list template is not yet created. You can try to do a loop and wait to be created
using(SPWeb web = site.OpenWeb())
{
SPListTemplate listTemplate = null;
while (listTemplate == null)
{
Thread.Sleep(1000);
try
{
listTemplate = web.ListTemplates["Custom Access List"];
if (listTemplate != null)
{
// here your code
web.Lists.Add("My new custom access list", "", listTemplate);
}
}
catch
{
web = site.OpenWeb();
}
}
}