In my xaml page i am using an image control
<Image x:Name="MyImage" Grid.Row="1" Stretch="UniformToFill" Source="{Binding SourceImage}"/>
like this. My question is. Is it possible to access this control's properties in my view model. "Like MyImage.Source =". If yes, how i can achieve similar implementation in windows phone.
Yes, you can, but you should not. The whole purpose of the ViewModel is to separate the logic into its own class rather than having it intermixed with view code.
Instead use the INotifyPropertyChanged interface to notify the UI that the image source has changed. If possible try to see if you can use a regular binding for the value you want to use, that will be the most robust and most easy way.
Another solution is to expose a interface on the view. Something like IView, you can probably come up with a more suitable name like IResetable.
interface IResetable
{
void Reset();
}
class MainWindow: Window, IResetable
{
publiv void Reset()
{
// Here you can access the view, but try to keep logic minimal.
}
}
class ViewModel
{
private readonly IResetable resetable;
public ViewModel(IResetable resetable)
{
_resetable = resetable;
}
void Foobar()
{
_resetable.Reset();
}
}
Related
I need to build a page which has some static "always there" elements, and some alternate groups of elements that show up and hide depending on user actions.
One approach could be combine each group of elements into some container like StackLayout and control their visibility, like this:
<StackLayout IsVisible="{Binding IsLoaded}">...</StackLayout>
<StackLayout IsVisible="{Binding IsLoaded}, Converter={helpers:InverseBoolConverter}">...</StackLayout>
However if there are more than 2 such groups of such elements, more overhead is added, and I feel like a better way of implementing that must exist.
What I've found is DataTemplateSelector, however: it seems to work for "list of items" type of controls. I wonder if something similar exists for a ContentPage or non-list controls, so I can define 2 or more alternative templates (or controls) with bindings to the same ViewModel inside, and based on the page ViewModel data switch their visibility: ViewModel.IsLoaded=true display one template/control, otherwise display the other.
Note: different control styling won't be enough for my scenario, it's different set of controls.
StackLayout can specify a DataTemplateSelector in its BindableLayout.ItemTemplateSelector:
<StackLayout BindableLayout.ItemsSource="{Binding Source}"
BindableLayout.ItemTemplateSelector="{StaticResource TemplateSelector}"/>
BindableLayout.ItemsSource might be a collection containing one item:
Source = new ObservableCollection<Model>
{
model
};
The DataTemplateSelector might be triggered as follows:
Source.Clear();
Source.Add(model);
Sample DataTemplateSelector:
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate1 { get; set; }
public DataTemplate DataTemplate2 { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
Model model = item as Model;
// Determine template; DataTemplate1 or DataTemplate2
return template;
}
}
Sample usage in XAML:
<DataTemplate x:Key="DataTemplate1">
...
<DataTemplate x:Key="DataTemplate2">
...
<namespace:TemplateSelector x:Key="TemplateSelector"
DataTemplate1="{StaticResource DataTemplate1}"
DataTemplate2="{StaticResource DataTemplate2}" />
My application viewModel responds to a user clicking a button to see test results:
private void AddDetailRows(List<QuizHistory> quizHistoryList)
{
quizDetails.Children.Clear();
quizDetails.Children.Add(AddData(quizHistoryList));
quizDetails.Children.Add(new LineTemplate());
}
Where quizDetails is the name of an element in the view.
But this doesn't work for me as the view model doesn't know what the view looks like and does not have access to the names of elements.
In a MVVM application, how is this problem solved?
You are completely right, that is not something that ViewModel is responsible of.
So, whatever you want to do with UI is not responsibility of the ViewModel.
If this is really the only option, then you can think of creating boolean properties in your VM and binding them to your views and then changing that boolean from false to true or vice versa on button click command which is binded to your VM.
To simplify it:
MyView.xaml
<StackLayout>
<Button Command="{Binding ShowHideQuizHistoryCommand}" ... />
<StackLayout x:Name="QuizHistory"
IsVisible={Binding ShowQuizHistory }>
//
</StackLayout>
</StackLayout>
MyViewModel.cs
private bool _showQuizHistory ;
public bool ShowQuizHistory
{
get { return _showQuizHistory ; }
set
{
_showQuizHistory = value;
OnPropertyChanged();
}
}
public ICommand ShowHideQuizHistoryCommand => new Command(() =>
{
ShowQuizHistory = !ShowQuizHistory;
});
So, this is just an example based on what you provided in question.
You can also use visual states, converters, triggers and behaviors in order to achieve this, but in my opinion this is the easiest way.
I originally implemented this feature but simply adding an image to a button. Then I realized I could simply add a tap gesture to an image (w/o using a button). Any recommendations which is the best way to go and why? Thanks.
I use my own "OnClick" event for Image :) with a custom control:
public class MyImage : Xamarin.Forms.Image
{
public static BindableProperty OnClickProperty =
BindableProperty.Create("OnClick", typeof(Command), typeof(MyImage));
public Command OnClick
{
get { return (Command)GetValue(OnClickProperty); }
set { SetValue(OnClickProperty, value); }
}
public MyImage()
{
GestureRecognizers.Add(new TapGestureRecognizer() {Command = new Command(DisTap)});
}
private void DisTap(object sender)
{
if (OnClick != null)
{
OnClick.Execute(sender);
}
}
}
Then use it with MVVM like:
<local:MyImage Source="{Binding Img}" OnClick="{Binding ImgTapCommand}" />
It depends of visual effect you want to achieve.
If you use Button you'll have tapped animation (depens of platform) and specific buttton border. You have much less control how the image will look like (it's on the left side of button text).
If you use a plain TapGestureRecognizer you'll have a normal image with full control of aspect ratio/size etc.
You could use absolute layout, which can be used to place two elements above each other, make sure to make the button is the second element.
<AbsoluteLayout>
<Image Source="clock.png" AbsoluteLayout.LayoutBounds="0.2,0.2,35,35" AbsoluteLayout.LayoutFlags="PositionProportional"/>
<Button AbsoluteLayout.LayoutBounds="0.2,0.2,35,35" AbsoluteLayout.LayoutFlags="PositionProportional" BorderColor="Transparent" BackgroundColor="Transparent" Command="{Binding AlertMeCommand}"/>
</AbsoluteLayout>
I have a UserControl that has a CheckBox on it. When I consume the UserControl on my main XAML page, I'd like to TwoWay bind a property on the control to a property on my ViewModel e.g.
<myUserControl BtnIsBlacklisted="{Binding IsBlacklisted, Mode=TwoWay}" />
When IsBlacklisted changes, I'd like my checkbox to change too and vice-versa.
Here is what I have,
public static readonly DependencyProperty BtnIsBlacklistedProperty =
DependencyProperty.Register("BtnIsBlacklisted",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(false, new
PropertyChangedCallback(BtnIsBlacklistedPropertyChanged))
);
private static void BtnIsBlacklistedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// ... do something here ...
}
public bool BtnIsBlacklisted
{
get { return (bool)GetValue(BtnIsBlacklistedProperty); }
set { SetValue(BtnIsBlacklistedProperty, value); }
}
My UserControl has this for the CheckBox,
<CheckBox x:Name="myCheckBox"
...
IsChecked="{Binding Path=BtnIsBlacklisted,
ElementName=UserControl,
Converter={StaticResource BoolToNotBool},
Mode=TwoWay}" />
The property on my ViewModel object is as follows,
public bool IsBlacklisted
{
get
{
return App.VM.BlacklistedRetailers.Contains(this.Retailer);
}
set
{
if (value)
{
App.VM.BlacklistedRetailers.Add(this.Retailer);
}
else
{
while (App.VM.BlacklistedRetailers.Contains(this.Retailer))
{
App.VM.BlacklistedRetailers.Remove(this.Retailer);
}
}
this.NotifyPropertyChanged("IsBlacklisted");
}
}
The only way BlacklistedRetailers changes is through the set method above so there is no need to trigger a NotifyPropertyChanged from there ...
I have tried many of the suggestions in other questions i.e.
using a dependency property
including Mode=TwoWay
Binding on the UserControl using a self-referencing DataContext set on the containing grid (this does not work either).
however none of these have worked.
Some final notes:
This is for a Windows Phone 7.5 project
Edit: One way binding doe not work either, it seems it there is a problem binding to the UserControl's own properties
An ElementName Binding matches against x:Name values which are in the same name scope as the element on which the binding is being set. There's not enough of the code shown to tell but you're using "UserControl" which I'm guessing is not set as the name of the element, but is being used to try and match the type. The ElementName also might not be able to resolve if the CheckBox is declared inside a template.
I've set up a viewmodel to bind a listcontrol to an ObservableCollection in my program. A UI control on the page adds and deletes objects to the collection, which works fine as the list is automatically updated.
After App-Switching and returning to the app, the buttons adds the objects, but the bindings seem to be lost. Any idea how I can maintain this even after returning? I don't really see the need to rebind the object (after defining it in XAML). Is there any way to foolproof this pattern, and ensure the bindings aren't lost upon returning to the app?
The XAML looks like this, but it's inside a UserControl - forgot to mention that
ItemsControl x:Name="PartyCollection" ItemTemplate="{StaticResource PartyCollectiontemplate}" ItemsSource="{Binding RoomParty, Source={StaticResource FormControlVM}}"
the codebehind looks like this
public class FormControlVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Party> RoomParty
{
get
{
return App.appData.currentChoices.roomParty;
}
set
{
App.appData.currentChoices.roomParty = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("RoomParty"));
}
}
}