I want to be able to add an event from my custom control to the outside world
as I change a value in my custom control, I use TwoWay, but I also want an event to be generated in my main app
I just cannot find anything relevant to this
the main app xaml contains this line :
<custom:MyComp x:Name="myComp" ValueChanged="SomeFunction" Value="{ Binding Path=someIntVariable, Mode=TwoWay}"></custom:MyComp>
how do I implement a ValueChanged event in the custom control ?
my control xaml:
<UserControl ...>
<StackPanel Orientation="Horizontal" >
...
<TextBox x:Name="MyCompTextBox" Width="50" Height="20" TextChanged="MyCompTextBox_TextChanged"/>
...
</StackPanel>
</UserControl>
the code
public partial class MyComp: UserControl
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(MyComp), new FrameworkPropertyMetadata(0, OnValuePropertyChangedCallback));
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValuePropertyChangedCallback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyComp mMyComp= source as MyComp;
int newValue=(int)e.NewValue;
myComp.MyCompTextBox.Text = newValue.ToString();
}
public MyComp()
{
InitializeComponent();
}
private void MyCompTextBox_TextChanged(object sender, EventArgs e)
{
int _bpm = int.ParseMyCompTextBox.Text);
Value = _bpm;
MyCompTextBox.Text = Value.ToString();
// here I want to trigger an even to the main app !!!
}
}
thanks for your help
Related
I have implemented Tabbedpage using ViewModel but my ViewModel constructor call 4 times because I create 4 tabs, I also used prism for ViewModel binding.
Below is a design file
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:material="clr-namespace:XF.Material.Forms.UI;assembly=XF.Material"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:ffTransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:extended="clr-namespace:Xamarin.Forms.Extended;assembly=Xamarin.Forms.Extended.InfiniteScrolling"
xmlns:customcontrols="clr-namespace:QuranicQuizzes.CustomControls"
xmlns:local="clr-namespace:QuranicQuizzes.Views" NavigationPage.HasNavigationBar="True"
x:Class="QuranicQuizzes.Views.DashboardPage">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="Dashboard" TextColor="White" HorizontalTextAlignment="Center" HorizontalOptions="CenterAndExpand" VerticalTextAlignment="Center" FontFamily="{StaticResource QuranFontBold}" FontSize="Medium" />
<StackLayout Orientation="Horizontal">
<material:MaterialMenuButton x:Name="Menus" ButtonType="Text" Image="list" TintColor="White" BackgroundColor="Transparent" CornerRadius="24" Choices="{Binding Actions}" MenuSelected="MaterialMenuButton_MenuSelected" />
</StackLayout>
</StackLayout>
</NavigationPage.TitleView>
<local:HomeTabPage/>
<local:QuizzesTabPage/>
<local:LiveGameTabPage/>
<local:AssignmentTabPage/>
</TabbedPage>
Below is my code
public partial class DashboardPage : TabbedPage
{
private DashboardPageViewModel vm;
public DashboardPage()
{
try
{
InitializeComponent();
vm = BindingContext as DashboardPageViewModel;
}
catch (Exception ex)
{
}
}
}
Below is my ViewModel
public class DashboardPageViewModel : ViewModelBase
{
INavigationService _navigationService;
IClientAPI _clientAPI;
Dashboards dashboard;
public DashboardPageViewModel(INavigationService navigationService, IClientAPI clientAPI) : base(navigationService)
{
_navigationService = navigationService;
_clientAPI = clientAPI;
if (CrossConnectivity.Current.IsConnected)
{
var StartDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
var Enddate = DateTime.Now.ToString("yyyy-MM-dd");
if (dashboard == null)
{
dashboard = new Dashboards();
getDashboardData(StartDate, Enddate);
}
}
}
}
I see what you're trying to do. You want to initialise your vm instance so that you can access you vm from your view.
Instead of doing this:
vm = BindingContext as DashboardPageViewModel;
what we can do is change the type of the existing BindingContext property by doing this:
public partial class DashboardPage
{
new DashboardPageViewModel BindingContext
{
get => (DashboardPageViewModel) base.BindingContext;
set => base.BindingContext = value;
}
public DashboardPage()
{
InitializeComponent();
}
}
now you can just access BindingContext.DoSomething because its type is now DashboardPageViewModel.
Now that's sorted out, your viewmodel should not be being called 4 times! Something is wrong here. Here is a checklist of things to do that may be causing the constructor being called 4 times as not a lot more info was provided.
Try removing <NavigationPage.TitleView> segment.
Make sure you are navigating to DashboardPage.
Make sure that each individual TabbedPage has it's own viewmodel.
Try removing prism:ViewModelLocator.AutowireViewModel="True"and manually adding the viewmodel to the TabbedPage.
Finally constructors should be able to run very fast and should only be used for assigning variables or instantiation or very quick operations. What you could maybe do is separate the code in your VM:
public class DashboardPageViewModel : ViewModelBase
{
IClientAPI _clientAPI;
Dashboards dashboard;
public DashboardPageViewModel(INavigationService navigationService, IClientAPI clientAPI) : base(navigationService)
{
_clientAPI = clientAPI;
}
public void Init()
{
if (CrossConnectivity.Current.IsConnected)
{
var StartDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
var Enddate = DateTime.Now.ToString("yyyy-MM-dd");
if (dashboard == null)
{
dashboard = new Dashboards();
getDashboardData(StartDate, Enddate);
}
}
}
}
and then in your view you could add this method:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if(BindingContext == null)
{
return;
}
BindingContext.Init();
}
I hope this really helps you.
NB: All this code was written on the fly and never compiled, there may be some errors.
I am trying to learn MVVVM, by doing a weather application, using a Udemy course as a reference, the Bing Maps Api, and the OpenWeather API.
I am trying to bind the city with the text of the AutoSuggestBox of my xaml
I have done the WeatherVM and bind it to the view, as a page resource
I also did a quick Method that gets me the City and the Country code, where I am (Using the Bing Maps api)
I Called the method from the MainPage.cs, only to see if it works, and it work fine I get the City and Country Code as expected
WeatherVM
public OpenWeather OpenWeather { get; set; }
private Task<string> _city;
public Task<string> city {
get { return _city; }
set {
_city = value;
GetLocationData();
}
}
public WeatherVM() {
OpenWeather = new OpenWeather();
}
private async void GetLocationData() {
var cityData = await MapLocator.GetCityData();
}
}
}
MainPage.xaml
x:Class="MVVM_Example.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVM_Example"
xmlns:vm="using:MVVM_Example.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<vm:WeatherVM x:Key="vm" />
</Page.Resources>
<Grid DataContext="{StaticResource vm}">
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Source={StaticResource vm}, Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Page>
I expect "Orlando" to appear in my AutoSuggestBox
When you use Binding, you need to set the DataContext of the page to be an instance of your binding source class instead of set the page.Resource. For more details, you can refer to this document.
<Page.DataContext>
<local:WeatherVM x:Name="viewModelInDataContext"/>
</Page.DataContext>
<Grid>
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
In code-behind, suppose you need to assign a value to the city.
this.viewModelInDataContext.city = "Orlando";
Update:
The ViewModel you showed has some issues. First, you need to assign a value to the cityData property, if not, the text of AutoSuggestBox which bound with the cityData will always display empty. Second, you need to implement INotifyChanged in WeatherVM. When your cityData changes, the text of the AutoSuggestBox will change. Third, do not set the property type to asynchronous like Task, this will affect the display, it's better to set string cityData.
public class WeatherVM : INotifyPropertyChanged
{
public WeatherVM(){
GetData();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _cityData;
public string cityData
{
get
{
return _cityData;
}
set
{
_cityData = value;
OnPropertyChanged();
}
}
private async void GetData()
{
cityData = await MapLocator.GetCityData();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm trying to implement TapGestureRecognizer which will be called in ViewModel (xaml.cs) not in View class...
Here is sample code in xaml file: (IrrigNetPage.xaml)
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:i18n="clr-namespace:agroNet.AppResource;assembly=agroNet"
xmlns:viewModels="clr-namespace:agroNet.ViewModel"
x:Class="agroNet.View.IrrigNetPage"
BackgroundColor="#EBEBEB">
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="HideListOnTap"/>
</Grid.GestureRecognizers>
</Grid>
I implemented HideListOnTap in xaml.cs page (view) like this: (IrrigNetPage.xaml.cs)
int visibility = 1;
private void HideListOnTap(object sender, EventArgs e)
{
visibility++;
if ((visibility % 2) == 0)
{
IrrigList.IsVisible = false;
}
else
{
IrrigList.IsVisible = true;
}
}
It's working fine, but how to do the same thing usin ViewModel?
(How to bind Gesture recognizer from (IrrigNetPage.xaml) with HideListOnTap in IrrigNetViewModel )
Use a Command whenever you want to handle some event in the ViewModel. Without passing any arguments the code would look like as follows
<!-- in IrrigNetPage.xaml -->
<TapGestureRecognizer Command="{Binding HideListOnTapCommand}"/>
And in the ViewModel IrrigNetPageViewModel.cs
public ICommand HideListOnTapCommand { get; }
public IrrigNetPageViewModel()
{
HideListOnTapCommand = new Command(HideListOnTap);
// if HideListOnTap is async create your command like this
// HideListOnTapCommand = new Command(async() => await HideListOnTap());
}
private void HideListOnTap()
{
// do something
}
I have some problem with correct binding my folder with images to master details and other operation with them.
So, I have model of folder and image
public class AppFolder
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
}
public class AppImage
{
private int id;
private string title;
private ImageSource appImageURL;
public AppImage() { }
public AppImage(string title, ImageSource imageSource)
{
Title = title;
AppImageURL = imageSource;
}
public int Id { get => id; set => id = value; }
public string Title { get => title; set => title = value; }
public ImageSource AppImageURL { get => appImageURL; set => appImageURL = value; }
}
And I bind List to Master/Details.
public class UserPhotosViewModel : ViewModelBase
{
private readonly IDataService dataService;
private readonly INavigationService navigationService;
public UserPhotosViewModel(IDataService dataService, INavigationService navigationService)
{
this.dataService = dataService;
this.navigationService = navigationService;
Initialize();
}
private async Task Initialize()
{
var item = new List<AppFolder>();
try
{
item = await dataService.GetDataList();
FolderList = item;
}
catch (Exception ex)
{
}
}
private List<AppFolder> folderList;
public List<AppFolder> FolderList
{
get { return folderList; }
set
{
folderList = value;
RaisePropertyChanged(nameof(FolderList));
}
}
}
Example xaml file
<controls:MasterDetailsView ItemsSource="{Binding FolderList}">
<controls:MasterDetailsView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"></TextBlock>
</DataTemplate>
</controls:MasterDetailsView.ItemTemplate>
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0"
>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
</controls:MasterDetailsView>
So, it's work correct and I saw my folders with images on page
enter image description here
Look nice and I think it all.
But when I want to add event and SelectedItem to AdaptiveGridView from MVVM model, I saw that it doesn't see them. Visual Studio show me that I could to write them in Model "AppFolder" but it's nonsens....
So, my question: How I can add event (binding command/method) to adaptive grid from UserPhotosViewModel?
Thank you for your time.
UPDATE
enter image description here
2) User double click on image and program send folder with this image and other to page and binding them to FlipView (imitation full screen viewer)
I try to add, but that way also offers to write event and property in model. I use this way for settings in navigation panel (parent control for master/details)
You just need to add command property in your AppFolder class like the following:
public class AppFolder:ViewModelBase
{
private long id;
private List<AppImage> appImages;
public AppFolder() { }
public List<AppImage> AppImages { get => appImages; set => appImages = value; }
public long Id { get => id; set => id = value; }
public RelayCommand<object> relayCommand { get; set; }
}
Then, in your UserPhotosViewModel, you could declare a method for initializing this command.
Since I do not know what the dataService.GetDataList() is. I just change this place in your code and make a simple code sample for you.
private void Initialize()
{
var item = new List<AppFolder>();
try
{
List<AppImage> ls = new List<AppImage>();
ls.Add(new AppImage() { Id = 1, Title = "aaa", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/1.jpg")) });
ls.Add(new AppImage() { Id = 2, Title = "bbb", AppImageURL = new BitmapImage(new Uri("ms-appx:///Assets/2.jpg")) });
item.Add(new AppFolder() { Id = 1, AppImages = ls,relayCommand=new RelayCommand<object>(DoubleTapCommand) });
FolderList = item;
}
catch (Exception ex)
{
}
}
private void DoubleTapCommand(object obj)
{
//the obj will be an AppFolder object
}
<controls:MasterDetailsView.DetailsTemplate>
<DataTemplate>
<controls:AdaptiveGridView x:Name="grid" ItemsSource="{Binding AppImages}"
OneRowModeEnabled="False"
ItemHeight="205"
DesiredWidth="205"
SelectionMode="Multiple"
Margin="0 6 0 0">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="DoubleTapped">
<Interactions:InvokeCommandAction Command="{Binding relayCommand}" CommandParameter="{Binding}"></Interactions:InvokeCommandAction>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<controls:AdaptiveGridView.ItemTemplate>
<DataTemplate >
<Grid Background="White" Margin="10">
<Image
Source="{Binding AppImageURL}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
/>
<TextBlock Text="{Binding TextTitle}"></TextBlock>
</Grid>
</DataTemplate>
</controls:AdaptiveGridView.ItemTemplate>
</controls:AdaptiveGridView>
</DataTemplate>
</controls:MasterDetailsView.DetailsTemplate>
1) User select N images and delete them (example)
About this requirement, I suggested that you'd better add a button to bind command for deleting selected items.
I use AutoCompleteBox in MVVM and i want to execute something only if the user click on the Item or if the user press Enter.
But now when I use the down\Up Key on the keyboard the selectedItem property changes...
My controls :
<Controls:AutoCompleteBox ItemsSource="{Binding IndicationDtos, Mode=TwoWay}"
Width="100" SelectedItem="{Binding IndicationSelected, Mode=TwoWay}"
ValueMemberPath="Diagnosis" Text="{Binding Criteria, Mode=TwoWay}" MinimumPopulateDelay="250"/>
What can I do to make the property "SelectedItem" is assigned only on Enter or click?
If you have any question...
thanks a lot
In your SelectedItem binding, you can use:
SelectedItem="{Binding IndicationSelected, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
That way selected item only changes when you focus on something else
I found solution i created new class.
Like this :
public class AutoCompleteBoxEx : AutoCompleteBox
{
public static readonly DependencyProperty SelectionBoxItemProperty =
DependencyProperty.Register(
"SelectionBoxItem",
typeof(object),
typeof(AutoCompleteBox),
new PropertyMetadata(OnSelectionBoxItemPropertyChanged));
public object SelectionBoxItem
{
get
{
return GetValue(SelectionBoxItemProperty);
}
set
{
SetValue(SelectionBoxItemProperty, value);
}
}
protected override void OnDropDownClosing(RoutedPropertyChangingEventArgs<bool> e)
{
base.OnDropDownClosing(e);
SelectionBoxItem = SelectionAdapter.SelectedItem;
}
private static void OnSelectionBoxItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
}