Passing binded item to MvxCommand - mvvmcross

Considering the following code:
<Mvx.MvxListView
android:id="#+id/items_list"
style="#style/ListNoDividers"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_above="#+id/footer_panel"
android:layout_below="#+id/intro_text"
local:MvxBind="ItemsSource Items;ItemClick DoItCommand"
local:MvxItemTemplate="#layout/item_template" />
I know that when I tap in item in the list, the DoItCommand will be invoked and the binded item will be past as a command parameter.
How can I use the same in a non MvxListView, like on this code snippet:
<LinearLayout
android:id="#+id/item1"
style="#style/ItemStyle"
local:MvxBind="Click DoItCommand, CommandParameter=PropertyInViewModel"
android:layout_marginBottom="#dimen/HalfDefaultInnerMargin" />
<LinearLayout
android:id="#+id/item1"
style="#style/ItemStyle"
local:MvxBind="Click DoItCommand, CommandParameter=OtherPropertyInViewModel"
android:layout_marginBottom="#dimen/HalfDefaultInnerMargin" />
Bottom line is that I need to pass a property value to DoItCommand using the command parameter.

As pointed out in the comments, using a similar approach to this, solves the issue!
public class MyLinearLayout : LinearLayout
{
public HhLinearLayout(Context context, IAttributeSet attrs)
: base(context, attrs)
{
Click += LinearLayoutClick;
}
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
private void LinearLayoutClick(object sender, EventArgs e)
{
var command = Command;
var commandParameter = CommandParameter;
if (command == null || !command.CanExecute(commandParameter))
{
return;
}
command.Execute(commandParameter);
}
}

Related

Tab Handling in Mvvmcross with MvxTabsFragmentActivity and MvxCachingFragmentStatePagerAdapter

I am writing an Android app with a tab. I was following the old method in the sample "FragmentSample". It was working fine but I am evaluating to switch to viewpager.
In FragmentSample:
TabViewModel creates an instance of viewmodel for each individual tab
(Vm1, Vm2...).
In TabView, each tab fragment (Tab1Fragment,
Tab2Fragment...) are explicitly associated to the viewmodel (Vm1,
Vm2...) created in TabViewModel.
It is perfect as I could do some navigation initialization to Vm1,
Vm2 in TabViewModel.
public class TabViewModel : BaseViewModel
{
public TabViewModel()
{
Vm1 = Mvx.IocConstruct<FirstTabViewModel>();
Vm2 = Mvx.IocConstruct<SecondTabViewModel>();
Vm3 = Mvx.IocConstruct<ThirdTabViewModel>();
}
public BaseViewModel Vm1 { get; set; }
public BaseViewModel Vm2 { get; set; }
public BaseViewModel Vm3 { get; set; }
}
public class TabView : MvxTabsFragmentActivity
{
public TabViewModel TabViewModel
{
get { return (TabViewModel)base.ViewModel; }
}
public TabView()
: base(Resource.Layout.Page_TabView, Resource.Id.actualtabcontent)
{
}
protected override void AddTabs(Bundle args)
{
AddTab<Tab1Fragment>("Tab1", "Tab 1", args, TabViewModel.Vm1);
AddTab<Tab2Fragment>("Tab2", "Tab 2", args, TabViewModel.Vm2);
// note that
AddTab<Tab3Fragment>("Tab3.1", "Tab 3.1", args, TabViewModel.Vm3);
AddTab<Tab3Fragment>("Tab3.2", "Tab 3.2", args, TabViewModel.Vm3);
AddTab<Tab3BigFragment>("Tab3.3", "Tab 3.3", args, TabViewModel.Vm3);
}
}
In the latest sample project "Example" in MvvmCross-All:
ExampleViewPagerStateViewModel create an instance of RecyclerViewModel
ExampleViewPagerStateFragment defines the tabs (RecyclerView
1...5) with MvxCachingFragmentStatePagerAdapter.
When MvxCachingFragmentStatePagerAdapter is executed, another
instance of RecyclerViewModel will be created
RecyclerViewModel created in ExampleViewPagerStateViewModel seems to
be completely irrelevant to the tab built. I commented out the
creation in ExampleViewPagerStateViewModel and there was no change to
the app behavior.
RecyclerViewModel was created twice. It is the same in
ExampleViewPagerFragment in the same project, and in the old version
of this sample XPlatformMenus.
public class ExampleViewPagerStateViewModel
: MvxViewModel
{
public RecyclerViewModel Recycler { get; private set; }
public ExampleViewPagerStateViewModel()
{
Recycler = new RecyclerViewModel();
}
}
public class ExampleViewPagerStateFragment : BaseStateFragment<ExampleViewPagerStateViewModel>
{
protected override int FragmentId => Resource.Layout.fragment_example_viewpager_state;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
var viewPager = view.FindViewById<ViewPager>(Resource.Id.viewpager);
if (viewPager != null)
{
var fragments = new List<MvxCachingFragmentStatePagerAdapter.FragmentInfo>
{
//new MvxCachingFragmentStatePagerAdapter.FragmentInfo("RecyclerView 1", typeof (RecyclerViewFragment),
// typeof (RecyclerViewModel)),
//new MvxCachingFragmentStatePagerAdapter.FragmentInfo("RecyclerView 2", typeof (RecyclerViewFragment),
// typeof (RecyclerViewModel)),
//new MvxCachingFragmentStatePagerAdapter.FragmentInfo("RecyclerView 3", typeof (RecyclerViewFragment),
// typeof (RecyclerViewModel)),
//new MvxCachingFragmentStatePagerAdapter.FragmentInfo("RecyclerView 4", typeof (RecyclerViewFragment),
// typeof (RecyclerViewModel)),
new MvxCachingFragmentStatePagerAdapter.FragmentInfo("RecyclerView 5", typeof (RecyclerViewFragment),
typeof (RecyclerViewModel))
};
viewPager.Adapter = new MvxCachingFragmentStatePagerAdapter(Activity, ChildFragmentManager, fragments);
}
var tabLayout = view.FindViewById<TabLayout>(Resource.Id.tabs);
tabLayout.SetupWithViewPager(viewPager);
return view;
}
}
My questions are:
What is the usage of creating RecyclerViewModel in
ExampleViewPagerStateViewModel in "Example"?
In FragmentSample, Tab3.1 Tab3.2 Tab3.3 are sharing the same Vm3. Can
I do the same thing with ViewPager? Is there any way I can specify
the tab view (RecyclerView 1...5) to associate to the
RecyclerViewModel created in ExampleViewPagerStateViewModel but not a
new instance?
Thanks.
Just found that it could be done by changing the third parameter of FragmentInfo to Recycler created in RecyclerViewModel. The sample should make the change.

How to write a function that can be available in all Razor views?

I'm trying to write a function that can bring a language resource from database in MVC 5 and Razor,
I want it to be very simple to use, for example, the following function should just get some text:
#T("ResourceType", "ResourceName")
I don't want to use #this. - just the the function name...
I saw some posts about it mentioning the line below, but still trying to understand how to do it
public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel>
Any help will be greatly appreciated.
Thanks in advance.
I finally found a way to do it, inspired by the NopCommerce project, see the code below.
The code can be used in any Razor (cshtml) view like this:
<h1>#T("StringNameToGet")</h1>
Also, note that pageBaseType needs to be updated with the correct new namespace,
this is the web.config in the Views folder - not the main one, should look like this:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="MyNameSpace.Web.Extensions.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="APE.Web" />
</namespaces>
</pages>
The code:
namespace MyNameSpace.Web.Extensions
{
public delegate LocalizedString Localizer(string text, params object[] args);
public abstract class WebViewPage : WebViewPage<dynamic>
{
}
/// <summary>
/// Update the pages element /views/web.config to reflect the
/// pageBaseType="MyNameSpace.Web.Extensions.WebViewPage"
/// </summary>
/// <typeparam name="TModel"></typeparam>
public abstract class WebViewPage<TModel> : System.Web.Mvc.WebViewPage<TModel>
{
private Localizer _localizer;
/// <summary>
/// Get a localized resources
/// </summary>
public Localizer T
{
get
{
if (_localizer == null)
{
//null localizer
//_localizer = (format, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args));
//default localizer
_localizer = (format, args) =>
{
var resFormat = SampleGetResource(format);
if (string.IsNullOrEmpty(resFormat))
{
return new LocalizedString(format);
}
return
new LocalizedString((args == null || args.Length == 0)
? resFormat
: string.Format(resFormat, args));
};
}
return _localizer;
}
}
public string SampleGetResource(string resourceKey)
{
const string resourceValue = "Get resource value based on resourceKey";
return resourceValue;
}
}
public class LocalizedString : System.MarshalByRefObject, System.Web.IHtmlString
{
private readonly string _localized;
private readonly string _scope;
private readonly string _textHint;
private readonly object[] _args;
public LocalizedString(string localized)
{
_localized = localized;
}
public LocalizedString(string localized, string scope, string textHint, object[] args)
{
_localized = localized;
_scope = scope;
_textHint = textHint;
_args = args;
}
public static LocalizedString TextOrDefault(string text, LocalizedString defaultValue)
{
if (string.IsNullOrEmpty(text))
return defaultValue;
return new LocalizedString(text);
}
public string Scope
{
get { return _scope; }
}
public string TextHint
{
get { return _textHint; }
}
public object[] Args
{
get { return _args; }
}
public string Text
{
get { return _localized; }
}
public override string ToString()
{
return _localized;
}
public string ToHtmlString()
{
return _localized;
}
public override int GetHashCode()
{
var hashCode = 0;
if (_localized != null)
hashCode ^= _localized.GetHashCode();
return hashCode;
}
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
return false;
var that = (LocalizedString)obj;
return string.Equals(_localized, that._localized);
}
}
}

MvxAutoCompleteTextView does not show dropdown list

When I enter "aa" in MvxAutoCompleteTextView. No dropdown list shown.
Anyone knows how to use MvxAutoCompleteTextView? No example in Mvvmcross NPlus1Days and Tutorials.Thanks
Layout
<MvxAutoCompleteTextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
local:MvxBind="Text SearchKey; ItemsSource ListAddresses; PartialText LabelTitle; SelectedObject Address" />
ViewModel
private string _SearchKey;
public string SearchKey
{
get { return _SearchKey; }
set { _SearchKey = value; RaisePropertyChanged(() => SearchKey); }
}
private List<string> _ListAddresses = new List<string>(){ "aa", "bb", "cc" };
public List<string> ListAddresses
{
get { return _ListAddresses; }
set { _ListAddresses = value; RaisePropertyChanged(() => ListAddresses); }
}
private string _LabelTitle;
public string LabelTitle
{
get { return _LabelTitle; }
set { _LabelTitle = value; RaisePropertyChanged(() => LabelTitle); }
}
private string _Address;
public string Address
{
get { return _Address; }
set { _Address = value; RaisePropertyChanged(() => Address); }
}
Here is an example that works:
https://github.com/JimWilcox3/MvxAutoCompleteTest
I had trouble with this control as well and Jim's example helped a lot. This answer warns against binding Text and I think that has some merit purely because for me the control was half working. When binding to Text the list view would appear but I could never bind SelectedObject or PartialText. I noticed I was receiving the following bind error:
Error - autoComplete is null in MvxAutoCompleteTextViewPartialTextTargetBinding
The simple fix for me was to change
<MvxAutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
... />
To
<Mvx.MvxAutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
... />

mvvmcross keep program settings using file plugin and json serialize/deserialize

I'm trying to use the fileplugin and json serializer to do this.
I have the following DcsSetup class, in the core project.
For now I'm working with droid. I can't seem to save the file. The json serialize is ok. The WriteFile seems ok, but next time I try to read the file using TryReadTextFile it fails.
I can't find the file on the device, so I think the WriteFile stuff is wrong.
What is the correct way to save and read my Settings class on Android?
public class DcsSetup
{
public class Settings
{
//Server
public string Server;
public int Port;
public int Device;
public string EncodingFromClient;
public string EncodingToClient;
public int FontCorrectionPixelsWidth; //Pixels to add or subtract i Y dimension to get the right font size
public int FontCorrectionPixelsHeight; //Pixels to add or subtract i Y dimension to get the right font size
public float XPct;//Pct to add to vertical placement of textBox and Buttons.
public float YPct;//Pct to add to horisontal placement of textBox and Buttons.
public float SizePct;//Pct to add to horisontal size of textBox and Buttons.
public bool FullScreen;
public bool DontSleep;
//Diverse
public bool AutoSendEnter;
}
public Settings Setting;
public DcsSetup()
{
var setupFound=true;
var fileService = Mvx.Resolve<IMvxFileStore>();
var jsonConvert = Mvx.Resolve<IMvxJsonConverter>();
var path = fileService.PathCombine("Setting", "Settings.txt");
Setting = new Settings();
try {
string settingFile;
if (fileService.TryReadTextFile(path, out settingFile)){
Setting = jsonConvert.DeserializeObject<Settings>(settingFile);
} else{
setupFound = false;
}
}
catch(Exception e) {
AppTrace.Error("Failed to read settings: {0}", e.Message);
setupFound=false;
}
if(setupFound==false){
Setting.Server = "192.168.1.100";
Setting.Port = 1650;
Setting.Device = 1;
Setting.EncodingFromClient = "CP1252";
Setting.EncodingToClient = "CP1252";
Setting.FontCorrectionPixelsWidth = 0;
Setting.FontCorrectionPixelsHeight = 0;
Setting.XPct = 97.0f;
Setting.YPct = 100.0f;
Setting.SizePct = 98.0f;
Setting.FullScreen = false;
Setting.DontSleep = true;
Setting.AutoSendEnter = true;
try {
//json
var json = jsonConvert.SerializeObject(Setting);
fileService.EnsureFolderExists("Setting");
fileService.WriteFile(path, json);
}
catch (Exception e) {
AppTrace.Error("Failed to save settings: {0}", e.Message);
}
}
}
}
}
I just created a project in VS2012 using the 3.1.1-beta2 packages for MvvmCross
I then added the File and Json plugin packages
I changed the core FirstViewModel to:
public class FirstViewModel
: MvxViewModel
{
private readonly IMvxFileStore _fileStore;
private readonly IMvxJsonConverter _jsonConverter;
private readonly string _filePath;
public class ToStore
{
public string Foo { get; set; }
}
public ICommand SaveCommand
{
get
{
return new MvxCommand(() =>
{
var toStore = new ToStore() {Foo = Hello};
var json = _jsonConverter.SerializeObject(toStore);
_fileStore.WriteFile(_filePath, json);
});
}
}
public ICommand LoadCommand
{
get
{
return new MvxCommand(() =>
{
string txt;
if (_fileStore.TryReadTextFile(_filePath, out txt))
{
Mvx.Trace("Loaded {0}", txt);
var stored = _jsonConverter.DeserializeObject<ToStore>(txt);
Hello = stored.Foo;
}
});
}
}
private string _hello = "Hello MvvmCross";
public FirstViewModel(IMvxFileStore fileStore, IMvxJsonConverter jsonConverter)
{
_fileStore = fileStore;
_jsonConverter = jsonConverter;
_filePath = _fileStore.PathCombine("SubDir", "MyFile.txt");
_fileStore.EnsureFolderExists("SubDir");
}
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
}
I called this from a test Android UI:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
android:text="Load"
local:MvxBind="Click LoadCommand"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
android:text="Save"
local:MvxBind="Click SaveCommand"
/>
</LinearLayout>
This seemed to work OK - it saved and loaded the JSON fine within and between test runs.
Based on this, my only guess is whether you are redeploying the app between runs - if you do this and you don't have MonoDroid's Preserve application data/cache on device between deploys checked, then you won't see the settings preserved.

Win 8.1 SearchBox - binding suggestions

We are writing a Windows 8.1 Store App that uses the new SearchBox XAML control. It looks like the only way to get suggestions into the dropdown list as the user types is to use the SearchBoxSuggestionsRequestedEventArgs and get the SearchSuggestionCollection from the event then append the suggestions to that.
We're using Prism for WinRT and want to separate the SearchBox and it's events from the ViewModel that is getting the list of suggestion strings.
I can't find anyway of binding a list of strings to the SearchSuggestionCollection or any way of adding them programatically that doesn't involve using the event args, which is making out unit testing very complex.
Is there a way of binding/adding the suggestions that doesn't involve the event args?
Okay, so I got obsessed with this question, and here is a solution for when using the SearchBox. I've uploaded a full sample on MSDN and GitHub
In short, use the Behavior SDK and and the InvokeCommand, and then use a converter to grab whatever data you need by using the new attributes InputConvert and InputConverterParameter.
XAML:
<SearchBox SearchHistoryEnabled="False" x:Name="SearchBox" Width="500" Height="50">
<SearchBox.Resources>
<local:SearchArgsConverter x:Name="ArgsConverter"/>
</SearchBox.Resources>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SuggestionsRequested">
<core:InvokeCommandAction
Command="{Binding SuggestionRequest}"
InputConverter="{StaticResource ArgsConverter}"
InputConverterLanguage="en-US"
InputConverterParameter="{Binding ElementName=SearchBox, Path=SearchHistoryEnabled}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
Converter:
public sealed class SearchArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = (SearchBoxSuggestionsRequestedEventArgs)value;
var displayHistory = (bool)parameter;
if (args == null) return value;
ISuggestionQuery item = new SuggestionQuery(args.Request, args.QueryText)
{
DisplayHistory = displayHistory
};
return item;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
Mainpade codebehind - of course you want this in a VM :)
public sealed partial class MainPage
{
public DelegateCommand<string> Search { get; set; }
public DelegateCommand<ISuggestionQuery> SuggestionRequest { get; set; }
public MainPage()
{
InitializeComponent();
Search = new DelegateCommand<string>(SearchedFor, o => true);
SuggestionRequest = new DelegateCommand<ISuggestionQuery>(SuggestionRequestFor, o => true);
DataContext = this;
}
private void SuggestionRequestFor(ISuggestionQuery query)
{
IEnumerable<string> filteredQuery = _data
.Where(suggestion => suggestion.StartsWith(query.QueryText,
StringComparison.CurrentCultureIgnoreCase));
query.Request.SearchSuggestionCollection.AppendQuerySuggestions(filteredQuery);
}
private readonly string[] _data = { "Banana", "Apple", "Meat", "Ham" };
private void SearchedFor(string queryText)
{
}
}
I wrote up a full walk through on my blog, but the above is all you really need :)