简体   繁体   中英

Pass value UserControl to ViewModel of View Page

Here is the complete Sample code
Using MVVM pattern My requirement is to have a ListView where

  1. If user Taps inside ListView on checkBox Storyboard Animation should play for True False and the ListView binded value should be updated in database. For true the tick should pop up with animation for false the tick should become invisible with animation.
    Solved as per @Elvis Xia answer
  2. If user taps on ListviewItem Navigate to new page with value
  3. Blueprint

Now I went with Usercontrol creation for the datatemplate. Here I want to identify both events separately user clicking on checkbox or clicking on Item separately.
Using ICommand I am creating two Delegates that gets binded to two transparent button which relays tapped event.
Dependency of creating transparent buttons and creating delgates while binding them made me think surely there must a better way in which I can utilize MVVM for these events without any code behind .

UserControl XAML

<Button Background="LightBlue" BorderBrush="White" BorderThickness="4" Command="{x:Bind sampleItem.itemTapped}" CommandParameter="{Binding}" HorizontalContentAlignment="Stretch">

    <Grid Margin="20">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Margin="20" HorizontalAlignment="Center" Text="{x:Bind sampleItem.sampleText}" FontSize="30"/>
        <Image Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_off.png" HorizontalAlignment="Right"/>
        <Image x:Name="image" Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_on.png" HorizontalAlignment="Right"  Visibility="Collapsed" RenderTransformOrigin="0.5,0.5">
            <Image.RenderTransform>
                <CompositeTransform/>
            </Image.RenderTransform>
        </Image>
        <Button x:Name="btnFav" Grid.Column="1"  Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{x:Bind sampleItem.favTapped}" CommandParameter="{Binding}">
            <Interactivity:Interaction.Behaviors>
                <!--<Core:EventTriggerBehavior EventName="Tapped">
                <Core:InvokeCommandAction Command="{Binding favTapped}" />
            </Core:EventTriggerBehavior>-->
                <Core:DataTriggerBehavior Binding="{Binding isFav}" Value="true">
                    <Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOn}"/>
                </Core:DataTriggerBehavior>
                <Core:DataTriggerBehavior Binding="{Binding isFav}" Value="false">
                    <Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOff}"/>
                </Core:DataTriggerBehavior>
            </Interactivity:Interaction.Behaviors>
        </Button>
    </Grid>
</Button>

UserControl XAML codeBehind

MainPageModel sampleItem { get { return this.DataContext as MainPageModel; } }
        public MainPageUserControl()
        {
            this.InitializeComponent();
            this.DataContextChanged += (s, e) => this.Bindings.Update();
        }

Viewmodel Code

public async Task GetData()
{
    for (int i = 0; i < 10; i++)
    {
        if (i == 3)
            sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true, favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)});
        else
            sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null, favTapped = new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped = new DelegateCommand<MainPageModel>(this.OnItemTapped) });
}
}
private void OnFavTapped(MainPageModel arg)
{
    if (arg.isFav == null) arg.isFav = true;
    else
        arg.isFav = !arg.isFav;
}
private void OnItemTapped(MainPageModel arg)
{
    System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
    System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
}

MainPage Xaml

 <Grid Grid.Row="1">
            <ListView  ItemsSource="{x:Bind ViewModel.sampleList}" IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
                <ListView.ItemTemplate>
                    <DataTemplate>                       
                            <userControls:MainPageUserControl/>                       
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>

There must be a better way to achieve the desired result using code behind.

The DataContext of every item in your project is an instance of MainPageModel class. So the favTapped command should be added to MainPageModel class. And it is a command, so favTapped should be an instance of a new class,which implements ICommand interface.

And if you don't want the animation to show at the page's first load, you can set isFav to bool? . And when the page first loads, set the value of isFav to null, thus it won't trigger the animation action.

Below are the Codes snippets and Demo Link:

ViewModelCommands.cs:

public class ViewModelCommands : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        //if it's a tapped event
        if (parameter is TappedRoutedEventArgs)
        {
            var tappedEvent = (TappedRoutedEventArgs)parameter;
            var gridSource = (Grid)tappedEvent.OriginalSource;
            var dataContext = (MainPageModel)gridSource.DataContext;
            //if tick is true then set to false, or the opposite.
            if (dataContext.isFav == null)
            {
                dataContext.isFav = true;
            } else
            {
                dataContext.isFav = !dataContext.isFav;
            }
        }
    }
}

MainPageModel.cs:

 public class MainPageModel:BindableBase
{

    public MainPageModel() {
        favTapped = new ViewModelCommands();
    }
    public ViewModelCommands favTapped { get; set; }
    private string _sampleText;
    public string sampleText
    {
        get
        {
            return this._sampleText;
        }
        set
        {
            Set(ref _sampleText, value);
        }
    }
    private bool? _isFav;
    public bool? isFav
    {
        get
        {
            return this._isFav;
        }
        set
        {
            Set(ref _isFav, value);
        }
    }
}

Here is the complete Demo: Demo Project

Update:

When using DelegateCommand , you can add the command Property to MainPageModel.cs and since the DataContext of the items are MainPageModel instances. You can use this.isFav to change the clicked item's value of isFav .

Here are the codes of MainPageModel.cs:

public class MainPageModel : BindableBase
{
    private DelegateCommand _favTapped;

    public DelegateCommand favTapped
    {
        get
        {
            if (_favTapped == null)
            {
                _favTapped = new DelegateCommand(() =>
                {
                    //Here implements the check on or off logic
                    this.CheckOnOff();
                }, () => true);
            }
            return _favTapped;
        }
        set { _favTapped = value; }
    }

    private void CheckOnOff()
    {
        if (this.isFav == null)
        {
            this.isFav = true;
        }
        else
        {
            this.isFav = !this.isFav;
        }
    }
    private string _sampleText;
    public string sampleText
    {
        get
        {
            return this._sampleText;
        }
        set
        {
            Set(ref _sampleText, value);
        }
    }
    private bool? _isFav;
    public bool? isFav
    {
        get
        {
            return this._isFav;
        }
        set
        {
            Set(ref _isFav, value);
        }
    }
}

For Listview item selected

You can use ListView.ItemClick Event. But you should also set IsItemClickEnabled="True",otherwise the event handler won't be fired.

For The subitem of Listview tapped

You can use Tapped Event of userControl.

Here are the Xaml codes, that shows how to register the above two events:

<Grid Grid.Row="1">
    <ListView IsItemClickEnabled="True" ItemClick="ListView_ItemClick_1" ItemsSource="{x:Bind ViewModel.sampleList}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <userControls:MainPageUserControl  Tapped="MainPageUserControl_Tapped"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>
public class ViewMOdel()
{ 
       public ViewModel()
    {
favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped)
 itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)
    }

public async Task GetData()
    {
        for (int i = 0; i < 10; i++)
        {
            if (i == 3)
                sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true});
            else
                sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null});
    }
    }
    private void OnFavTapped(MainPageModel arg)
    {
        if (arg.isFav == null) arg.isFav = true;
        else
            arg.isFav = !arg.isFav;
    }
    private void OnItemTapped(MainPageModel arg)
    {
        System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
        System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
    }
    }

Binding should be like this

<Button x:Name="btnFav" Grid.Column="1"  Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>

Update

<ListView  ItemsSource="{x:Bind ViewModel.sampleList}"  x:Name="Listview"IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate>                       
                                <userControls:MainPageUserControl Tag="{Binding DataContext,ElementName=Listview}"/>                       
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
 <Button x:Name="btnFav" Grid.Column="1"  Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>

Update2 using EventTriggerBehavior

 favTapped = new DelegateCommand<RoutedEventArgs>(this.OnFavTapped);

private void OnFavTapped(RoutedEventArgs arg)
        {
         var item =  (( arg.OriginalSource )as Button).DataContext as MainPageModel
        }

<Button n x:Name="btnFav" Grid.Column="1"  Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent"   >
            <interact:Interaction.Behaviors>

                <interactcore:EventTriggerBehavior EventName="Click" >
                    <interactcore:InvokeCommandAction Command="{Binding ElementName=usercontrol, Path=Tag.favTapped}" />
                </interactcore:EventTriggerBehavior>
            </interact:Interaction.Behaviors>
        </Button>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM