I need to get my custom activity's InArgument value at ActivityDesigner.
MyActivity:
[Designer(typeof(ReadTextDesigner))]
public sealed class ReadText : CodeActivity
{
public InArgument<string> ImageName { get; set; }
protected override void Execute(CodeActivityContext context)
{
}
}
My designer:
public partial class ReadTextDesigner
{
public ReadTextDesigner()
{
InitializeComponent();
//this.ModelItem is null here.. WHY is it null?
//How do I get Activity's ImageName here?
}
}
I also have a button as in the image below and when you click on it, I CAN SET my custom Activity's value like this:
private void BtnStart_OnClick(object sender, RoutedEventArgs e)
{
this.ModelItem?.Properties["ImageName"]?.SetValue(new InArgument<string>()
{
Expression = "some value"
});
}
XAML:
<sapv:ExpressionTextBox
Expression="{Binding Path=ModelItem.ImageName, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In }"
ExpressionType="s:String"
HintText="Enter a string"
OwnerActivity="{Binding Path=ModelItem}"
Width="110"
Margin="0,5"
Grid.Row="0"
MaxLines="1"
x:Name="TxtImagePath"/>
<Button Grid.Column="0" Grid.Row="1" Content="Get Image" HorizontalAlignment="Center" Click="BtnStart_OnClick" x:Name="BtnStart"/>
How do I GET Activity InArgument ReadTextDesigner constructor?
Its quite weird but I found a workaround. Although this is A solution, I'm hoping for a much better one;
Since within the constructor, I cannot get the ModelItem, I created a new Thread apart from the Main Thread. This new Thread waits 2 milliseconds and then tries to get ModelItem and it succeeds somehow.
Here is the new modified ReadTextDesigner code (Note: I only changed ReadTextDesigner code nothing else)
public ReadTextDesigner()
{
InitializeComponent();
new TaskFactory().StartNew(() => { this.Dispatcher.Invoke(() => SetImage(this)); });
}
private void SetImage(ReadTextDesigner designer)
{
Thread.Sleep(2);
if (designer.ModelItem.GetCurrentValue() is ReadText readText)
{
var imageName = readText.ImageName?.Expression?.Convert<string>();
if (!string.IsNullOrWhiteSpace(imageName))
{
//imageName has a value at this point!
}
}
}
ModelItem is not null anymore and carries the necessary value.
Hope this will help someone or someone post a better solution.
Cheers!
Related
I tried to add a search bar in the ListView but it failed.
after clicking debug the program, it shows the error like "does not contain a defination for "Contains" ... in the part of code behind
I would like to for help for this. Thanks
in the code behind :
public partial class ListPage : ContentPage
{
public ListPage()
{
InitializeComponent();
listView.ItemsSource = LoadData();
private void SearchBar_Pressed(object sender, EventArgs e)
{
var keyword = MainSearchBar.Text;
listView.ItemsSource = LoadData().Where(name => name.Contains(keyword));
}
//in the following codes is about the function LoadData()
which includes the information in the ListView
it is expected that I can search the keyword in the search bear how the error is "Error CS1929: 'Video' does not contain a definition for 'Contains' and the best extension method overload 'Enumerable.Contains(IEnumerable, string)' requires a receiver of type 'IEnumerable' (CS1929) (MVDemo)"
Please help for it. Thanks
Yes, ok, thanks, let me post other parts in this program. For the LoadDate(), The code behind is :
#region "LoadData"
protected IList<Video> LoadData()
{
var videos = new List<Video>();
videos.Add(new Video
{
ID = "27/items/Final_201903/心相契合Final.mp4",
Name = "心相契合",
Writer = "璞園詩歌",
Photo = ImageSource.FromFile("Heart.png")
});
videos.Add(new Video
{
ID = "2/items/20191006_769/明光照耀.mov",
Name = "明光照耀",
Writer = "璞園詩歌",
Photo = ImageSource.FromFile("Light.png")
});
return videos;
}
#endregion
in the xaml:
<StackLayout>
<SearchBar x:Name="MainSearchBar" SearchButtonPressed="SearchBar_Pressed"/>
<ListView IsPullToRefreshEnabled="True" x:Name = "listView" ItemTapped="Handle_ItemTapped" HasUnevenRows="true" SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Text="{Binding Name}"
Detail="{Binding Writer}"
ImageSource="{Binding Photo}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I made the page for the class "Video" in file "Models" , the code is:
public class Video
{
public Video()
{
}
public string Name { get; set; }
public ImageSource Photo { get; set; }
public string Writer { get; set; }
public string ID { get; set; }
}
Please help me for it. In fact, I'm new in the Xamarin. Thanks
If you want to filter the Name, please try the code below.
Change:
listView.ItemsSource = LoadData().Where(name => name.Contains(keyword));
To: The Name is what you defined in Video class.
listView.ItemsSource = LoadData().Where(name => name.Name.Contains(keyword));
I make the sample to test, it works well on my side.
Result:
your LINQ query has a syntax error
listView.ItemsSource = LoadData().Where(x => x.name.Contains(keyword));
My Xamarin Forms ViewModel looks like this:
public class CFSPageViewModel : BaseViewModel
{
#region Constructor
public CFSPageViewModel()
{
PTBtnCmd = new Command<string>(PTBtn);
OnTappedCmd = new Command<string>(OnTapped);
}
#endregion
# region Commands
public ICommand PTBtnCmd { get; set; }
public ICommand OnTappedCmd { get; }
#endregion
#region Methods
private void OnTapped(string btnText)
{
Utils.SetState(btnText, CFS, SET.Cfs);
CFSMessage = Settings.cfs.TextLongDescription();
}
private void PTBtn(string btnText)
{
Utils.SetState(btnText, PT);
SetLangVisible(btnText);
SetLangSelected(btnText);
CFSMessage = Settings.cfs.TextLongDescription();
}
}
I was previously sending a message with MessageCenter to my C# back end code but now have removed MessageCenter so the methods are part of the ViewModel.
Is this a safe thing to do? I heard that MessageCenter messages passing around between ViewModels for everything was not the best of solutions.
Note that here is the way I had been doing it before:
MyPageViewModel.cs
PTBtnCmd = new Command<Templates.WideButton>((btn) =>
MessagingCenter.Send<CFSPageViewModel, Templates.WideButton>(
this, "PTBtn", btn));
MyPage.xaml.cs
MessagingCenter.Subscribe<CFSPageViewModel, Templates.WideButton>(
this, "PTBtn", (s, btn) =>
{
Utils.SetState(btn.Text, vm.PT);
SetLangVisible(btn.Text);
SetLangSelected(btn.Text);
vm.CFSMessage = Settings.cfs.TextLongDescription();
});
Note that methods such as SetLangVisible were also in MyPage.xaml.cs
To add an event handler to your Buttonsimply:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyProject.Views.MyPage">
<ContentPage.Content>
<StackLayout>
<Button Text="PTBtn" Clicked="Handle_Clicked" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
In code behind:
namespace MyProject.Views
{
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
}
void Handle_Clicked(object sender, EventArgs eventArgs)
{
((Button)sender).BackgroundColor = Color.Blue; // sender is the control the event occured on
// Here call your methods depending on what they do/if they are view related
/*
Utils.SetState(btn.Text, vm.PT);
SetLangVisible(btn.Text);
SetLangSelected(btn.Text);
vm.CFSMessage = Settings.cfs.TextLongDescription();
*/
}
}
}
All the events that can have a event handler assigned to it is listed in yellow with E:
The Command fires first and you can add CanExecute as a second parameter in the constructor - which will also stop both the command and the event handler from being executed.
I would also rename the Command to something like SelectLanguageCommand - to distinguish it from a ui action. That way you can disconnect the button from the command and connect the command to other ui - if you decide you want to change the view in the future. It would also be easier to understand when unit testing.
Is this a safe thing to do? I heard that MessageCenter messages passing around between ViewModels for everything was not the best of solutions.
You could register all your view models with DependencyService
public App()
{
InitializeComponent();
DependencyService.Register<AboutViewModel>();
DependencyService.Register<CFSPageViewModel>();
DependencyService.Register<MyPageViewModel>();
MainPage = new AppShell();
}
Set BindingContext of the views to the instances registered:
public AboutPage()
{
InitializeComponent();
BindingContext = DependencyService.Get<AboutViewModel>();
}
And get the ViewModel instance anywhere you need it. That way you don't have to deal with subscriptions as you need to when using MessagingCenter.
Wether it is safe or not - I am not sure.
I have Button in ListView Cell. On button click I need to perform two actions from ViewModel
Get the current record (to make modification in data)
Get the current Cell/View (to modify text, bg color of button)
However, I am able to perform single action at a time using DelegateCommand by passing Student and object param respectively. See my code below
public StudentAttendanceListPageViewModel()
{
//BtnTextCommand=new DelegateCommand<object>(SetBtnText);
ItemCommand=new DelegateCommand<Student>(BtnClicked);
}
public DelegateCommand<object> BtnTextCommand { get; private set; }
public void SetBtnText(object sender)
{
if (view.Text == "P")
{
view.Text = "A";
view.BackgroundColor= (Color)Application.Current.Resources["lighRedAbsent"];
}
}
public DelegateCommand<Student> ItemCommand { get; }
public void BtnClicked(Student objStudent)
{
objStudent.AbsentReasonId="1001";
objStudent.AttendanceTypeStatusCD = "Absent";
objStudent.AttendanceTypeStatusId = "78001"
}
This is Button code
<Button x:Name="mybtn"
Command="{Binding Source={x:Reference ThePage}, Path=BindingContext.ItemCommand}"
CommandParameter="{Binding .}"
BackgroundColor="{DynamicResource CaribGreenPresent}"
Text="{Binding AttendanceTypeStatusId, Converter={x:StaticResource IDToStringConverter}}">
</Button>
If you see above code I have two methods SetBtnText and BtnClicked. How can I merge these two methods into one by passing Student and object params at a time in DelegateCommand?
You should bind the view's properties to the view model. Then pass the view model as command parameter and change whatever you want to change in the command and data binding will automatically update the view.
Example:
<Button Command="{Binding SomeCommand}"
Text="{Binding Text}">
</Button>
public class StudentViewModel
{
public StudentViewModel( Student student )
{
_text = $"Kick {student.Name}";
SomeCommand = new DelegateCommand( () => {
Text = "I have been kicked"
student.Exmatriculate();
SomeCommand.RaiseCanExecuteChanged();
},
() => student.IsMatriculated
);
}
public DelegateCommand SomeCommand { get; }
public string Text
{
get => _text;
set => SetProperty( ref _text, value );
}
private string _text;
}
As stated in the comments already, it's never ever necessary to pass the view to the view model. To me, it looks as if you don't have a view model in the first place, though, as your code only mentions Student (which most likely is part of the model), while there's no occurence of a StudentViewModel. You know, you do not bind to the model directly unless it's a trivial toy project.
I'm developing my Xamarin app with the MVVM pattern. I want to display an alert to the user when the user presses a button.
I declare my ViewModel with
class MainPageViewModel : BindableBase {
Unfortunately, I'm not able to access a Page object from within the ViewModel directly. How do I best go about displaying my alert?
Late to the party but as Nick Turner has mentioned in a number of comments, the solutions given so far require the view model to reference the view which is an antipattern/infringement of MVVM. Additionally, you'll get errors in your view model unit tests such as: You MUST call Xamarin.Forms.Init(); prior to using it.
Instead you can create an interface that contains the code for your alert boxes and then use in your view model as follows:
Interface:
public interface IDialogService
{
Task ShowAlertAsync(string message, string title, string buttonLabel);
}
Implementation (Using ACR.UserDialogs NuGet package):
public class DialogService : IDialogService
{
public async Task ShowAlertAsync(string message, string title, string buttonLabel)
{
if (App.IsActive)
await UserDialogs.Instance.AlertAsync(message, title, buttonLabel);
else
{
MessagingCenter.Instance.Subscribe<object>(this, MessageKeys.AppIsActive, async (obj) =>
{
await UserDialogs.Instance.AlertAsync(message, title, buttonLabel);
MessagingCenter.Instance.Unsubscribe<object>(this, MessageKeys.AppIsActive);
});
}
}
}
ViewModel:
public class TestViewModel
{
private readonly IDialogService _dialogService;
public TestViewModel(IDialogService dialogService)
{
//IoC handles _dialogService implementation
_dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
}
public ICommand TestCommand => new Command(async () => await TestAsync());
private async Task TestAsync()
{
await _dialogService.ShowAlertAsync("The message alert will show", "The title of the alert", "The label of the button");
}
}
TestCommand can then be bound to the button in the your xaml:
<Button x:Name="testButton" Command="{Binding TestCommand}">
To display Alert write below code in your ViewModel class
public class MainViewModel
{
public ICommand ShowAlertCommand { get; set; }
public MainViewModel()
{
ShowAlertCommand = new Command(get => MakeAlter());
}
void MakeAlter()
{
Application.Current.MainPage.DisplayAlert("Alert", "Hello", "Cancel", "ok");
}
}
Set your Command to Button in xaml
<StackLayout>
<Button Text="Click for alert" Command="{Binding ShowAlertCommand}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
Set BindingContext in code behind of your xaml file. If you xaml file MainPage.xaml
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
You can call the below code within the view model, if you are using normal MVVM pattern.
App.current.MainPage.DisplayAlert("","","");
You can use Prism's PageDialogService which keeps your ViewModels very clean and testable.
For Shell application I was able to achieve like this
await Shell.Current.DisplayAlert("Title", "Message", "Cancel");
Would it be a good idea to create popups like this? (called from my ViewModel)
private void OpenPopUp()
{
Application.Current.MainPage.Navigation.ShowPopup(new CustomPopUp());
}
You can find a guide on how to create custom pupups here:
https://www.youtube.com/watch?v=DkQbTarAE18
Works pretty good for me, but I am very new to Xamarin.
Ive saved listviews previously like this so I was hoping I can use the same strategy:
This is my viewmodel where I save all the strings in the class "Notes". Even tho I added the entry binding to the Notes class it doesnt save.:
private void Save()
{
var art = Memory.Favs;
art.Add(Notes);
Memory.Favs = art;
}
This is my memory class:
public static class Memory
{
public static List<Notes> FavoriteNotes
{
get
{
if (Application.Current.Properties.ContainsKey("favoritenotes"))
return JsonConvert.DeserializeObject<List<Notes>>(Application.Current.Properties["favoritenotes"].ToString());
else
return new List<Notes>();
}
set
{
Application.Current.Properties["favoritenotes"] = JsonConvert.SerializeObject(value);
}
}
}
}
Now to the tricky part, I have an entry in xaml where i need to save data in the Save() void.
My xaml:
<Entry Placeholder="{Binding PlaceholderText}" HeightRequest="200" Text="{Binding TextSaveArt, Mode=TwoWay}" />