Is it possible to have multiple ContentPresenters in a ControlTemplate?
I created a CustomControl with two BindableProperties of type View: ReadonlyContent and WritableContent.
The ControlTemplate Wraps two ContentPresenters where the Content is bound to either ReadonlyContent or WritableContent.
Misteriously it only shows the content of one ContentPresenter in that case always ReadonlyContent uneffected by order of ContentPresenters or whatever.
So the question again: Is it possible to have two or more ContentPresenters in a ControlTemplate?
The ControlTemplate looks like this:
<ContentView.ControlTemplate>
<ControlTemplate>
<Grid HorizontalOptions="Fill" VerticalOptions="Fill" RowSpacing="0" Margin="0" Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="40" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="2"
ImageSource="{TemplateBinding IsReadonly, Converter={StaticResource BooleanToImageSourceConverter}}"
BackgroundColor="Transparent"
WidthRequest="40"
HeightRequest="25"
Padding="0"
Clicked="OnToggleIsReadonly"
x:Name="btnToggleEditMode"
Margin="0" />
<StackLayout Grid.Column="1" Orientation="Vertical">
<ContentPresenter Content="{TemplateBinding ReadonlyContent, Mode=OneWay}" />
<ContentPresenter Content="{TemplateBinding WriteableContent, Mode=OneWay}" />
</StackLayout>
</Grid>
</ControlTemplate>
</ContentView.ControlTemplate>
while the code behind of the control looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ActivatableContent : ContentView
{
public static readonly BindableProperty IsReadonlyProperty = BindableProperty.Create(
"IsReadonly",
typeof(bool),
typeof(ActivatableContent),
true,
BindingMode.TwoWay,
propertyChanged: OnIsReadonlyChanged);
public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnReadonlyContentChanged);
public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(ActivatableContent), propertyChanged: OnWritableContentChanged);
public bool IsReadonly
{
get { return (bool)GetValue(IsReadonlyProperty); }
set
{
SetValue(IsReadonlyProperty, value);
}
}
public View ReadonlyContent
{
get { return (View)GetValue(ReadonlyContentProperty); }
set { SetValue(ReadonlyContentProperty, value); }
}
public View WritableContent
{
get { return (View)GetValue(WritableContentProperty); }
set { SetValue(WritableContentProperty, value); }
}
public ActivatableContent()
{
InitializeComponent();
}
private static void OnIsReadonlyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
((ActivatableContent)bindable).IsReadonly = (bool)newvalue;
}
private static void OnReadonlyContentChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var readonlyContent = (View)newvalue;
((ActivatableContent)bindable).ReadonlyContent = readonlyContent;
SetInheritedBindingContext(readonlyContent, bindable.BindingContext);
}
private static void OnWritableContentChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var writableContent = (View)newvalue;
((ActivatableContent)bindable).WritableContent = writableContent;
SetInheritedBindingContext(writableContent, bindable.BindingContext);
}
private void OnToggleIsReadonly(object sender, EventArgs e)
{
IsReadonly = !IsReadonly;
}
/// <summary>Method that is called when the binding context changes.</summary>
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var controlTemplate = ControlTemplate;
if (ReadonlyContent != null && controlTemplate != null)
{
SetInheritedBindingContext(ReadonlyContent, BindingContext);
}
if (WritableContent != null && controlTemplate != null)
{
SetInheritedBindingContext(WritableContent, BindingContext);
}
}
}
ContentPresenter always points to the control's content by default you can't define two different content.
However, we could do this in custom control. You could download folder of ContentPresenterDemo from GitHub for reference.
https://github.com/WendyZang/Test.git
First, define two different bindable properties in your custom control
public static readonly BindableProperty ReadonlyContentProperty = BindableProperty.Create(nameof(ReadonlyContent), typeof(View), typeof(CustomContentView));
public View ReadonlyContent
{
get { return (View)GetValue(ReadonlyContentProperty); }
set { SetValue(ReadonlyContentProperty, value); }
}
public static readonly BindableProperty WritableContentProperty = BindableProperty.Create(nameof(WritableContent), typeof(View), typeof(CustomContentView));
public View WritableContent
{
get { return (View)GetValue(WritableContentProperty); }
set { SetValue(WritableContentProperty, value); }
}
Please note, do not forget to change ContentPage to ContentView in xaml.
And then define two views with template in Application.Resources.
<Application.Resources>
<ControlTemplate x:Key="MyTemplate">
<StackLayout>
<ContentView Content="{TemplateBinding WritableContent}"/>
<ContentView Content="{TemplateBinding ReadonlyContent}"/>
</StackLayout>
</ControlTemplate>
<ContentView x:Key="MyContentView">
<StackLayout>
<Label Text="MyContentView" BackgroundColor="Red"></Label>
<!--code here...-->
</StackLayout>
</ContentView>
<ContentView x:Key="MyContentView2">
<StackLayout>
<Label Text="MyContentView2" BackgroundColor="Green"></Label>
<!--code here...-->
</StackLayout>
</ContentView>
And then use it in page.
<StackLayout>
<local:CustomContentView ReadonlyContent="{StaticResource MyContentView}"
WritableContent="{StaticResource MyContentView2}"
ControlTemplate="{StaticResource MyTemplate}" />
</StackLayout>
Or you could use Picker to do Multiple ContentPresenters.
Define a Picker with multiple ContentPresenters.
<Picker x:Name="picker" Title="Select a template" SelectedIndexChanged="SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Template 1</x:String>
<x:String>Template 2</x:String>
<x:String>Template 3</x:String>
<x:String>Template 4</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
You could download from the GitHub.
https://github.com/CrossGeeks/ControlTemplateSample
Related
As a not so experienced Xamarin developer I am trying to retrieve a property value from a child page (ContentView) in my Parent page (ContentPage).
I can find quite some examples how to get / set the value from the parent page to the child page but not the other way around.
Some more details:
In my ContentPage I have a CarouselView, this CarouselView has a DataTemplate which contains a ContentView, this ContentView has also a CarouselView inside it with 2 layers / 2 vertical carousel items.
When the position of the CarouselView inside the ContentView (the child page), changes position to the second item, the IndicatorView in the parent page should be set to invisible.
I am not so experienced using a BindableProperty but I think that is the way to go. I got it setup as following for now:
The parent page / ContentPage:
<local:ExtendedCarouselView
x:Name="carousel"
HorizontalScrollBarVisibility="Never"
IndicatorView="activityIndicatorView"
IsScrollAnimated="False"
ItemsSource="{Binding ActivityData}"
Position="{Binding Position, Mode=TwoWay}"
VerticalOptions="FillAndExpand">
<local:ExtendedCarouselView.ItemTemplate>
<DataTemplate>
<Frame Style="{StaticResource CarouselWorkaround}">
<local:PCSActivityOverviewTemplate x:Name="testy" />
</Frame>
</DataTemplate>
</local:ExtendedCarouselView.ItemTemplate>
</local:ExtendedCarouselView>
<IndicatorView
x:Name="activityIndicatorView"
Padding="0,0,0,30"
IndicatorColor="{DynamicResource TranslucidBlack}"
IsVisible="{Binding InnerCarouselViewPosition, Converter={StaticResource IndicatorVisibilityConverter}, Mode=TwoWay}"
SelectedIndicatorColor="{DynamicResource BaseTextColor}"
VerticalOptions="Start" />
The child page / ContenView (XAML):
<ContentView.Content>
<CarouselView
x:Name="carousel"
ItemsSource="{Binding ., Converter={StaticResource OneToManyConverter}, ConverterParameter=2}"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
PositionChanged="carousel_PositionChanged"> <!-- The event which should change the property 'InnerCarouselViewPosition' -->
<CarouselView.ItemTemplate>
<grial:IntMemberTemplateSelector MemberName="Position">
<grial:IntMemberTemplateSelector.Items>
<!-- CAROUSEL'S PAGE 0 -->
<grial:IntMemberTemplateSelectorItem Value="0">
<DataTemplate>
<!-- Other elements... -->
</DataTemplate>
</grial:IntMemberTemplateSelectorItem>
<!-- CAROUSEL'S PAGE 1 -->
<grial:IntMemberTemplateSelectorItem Value="1">
<DataTemplate>
<!-- Other elements... -->
</DataTemplate>
</grial:IntMemberTemplateSelectorItem>
</grial:IntMemberTemplateSelector.Items>
</grial:IntMemberTemplateSelector>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentView.Content>
The ContenView (C#/.cs):
public partial class PCSActivityOverviewTemplate : ContentView
{
public static BindableProperty CurrentChildCarouselViewLocationProperty =
BindableProperty.Create(
nameof(CurrentChildCarouselViewLocationProperty),
typeof(int),
typeof(CarouselView),
defaultValue: 1);
public int CurrentChildCarouselViewLocation
{
get { return (int)GetValue(CurrentChildCarouselViewLocationProperty); }
set { SetValue(CurrentChildCarouselViewLocationProperty, value); }
}
private void carousel_PositionChanged(object sender, PositionChangedEventArgs e)
{
CarouselView _carouselView = (CarouselView)sender;
CurrentChildCarouselViewLocationProperty = _carouselView.Position;
}
... code omitted
}
When the inner carousel view position's changes then the bindable property should be set, this property should be used in the parent page to set the indicator view visible / invisible using a converter (position 0 = visible, position 1 = invisible).
For some, probably a quite obvious, reason the above is not working.
The Visibility converter:
public class CarouselIndicatorVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 1;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 0 : 1;
}
}
UPDATE*
I also tried to use a binding as the Position Property in my ViewModel, while the binding value changes, I cant access it in the Parent page nothing happens, the converter is not triggered), I removed the bindable property.
The new Content page XAML (the parent Carousel):
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
...
>
<ContentPage.Resources>
<ResourceDictionary>
<local:CarouselIndicatorVisibilityConverter x:Key="IndicatorVisibilityConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<Grid>
<local:ExtendedCarouselView
x:Name="carousel"
HorizontalScrollBarVisibility="Never"
IndicatorView="activityIndicatorView"
IsScrollAnimated="False"
ItemsSource="{Binding ActivityData}"
Position="{Binding Position, Mode=TwoWay}"
VerticalOptions="FillAndExpand">
<local:ExtendedCarouselView.ItemTemplate>
<DataTemplate>
<Frame Style="{StaticResource CarouselWorkaround}">
<local:PCSActivityOverviewTemplate x:Name="testy" />
</Frame>
</DataTemplate>
</local:ExtendedCarouselView.ItemTemplate>
</local:ExtendedCarouselView>
<IndicatorView
x:Name="activityIndicatorView"
Padding="0,0,0,30"
IndicatorColor="{DynamicResource TranslucidBlack}"
IsVisible="{Binding BindingContext.CurrentChildCarouselViewLocation, Source={x:Reference carousel}, Converter={StaticResource IndicatorVisibilityConverter}, Mode=TwoWay}"
SelectedIndicatorColor="{DynamicResource BaseTextColor}"
VerticalOptions="Start" />
</Grid>
</Grid>
</ContentPage.Content>
</ContentPage>
XAML of the child page:
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
...
>
<ContentView.Content>
<CarouselView
x:Name="carousel"
IsBounceEnabled="False"
ItemsSource="{Binding ., Converter={StaticResource OneToManyConverter}, ConverterParameter=2}"
Position="{Binding CurrentCarouselViewLocation}"
PositionChanged="carousel_PositionChanged"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never">
<CarouselView.ItemsLayout>
<LinearItemsLayout
ItemSpacing="0"
Orientation="Vertical"
SnapPointsAlignment="Start"
SnapPointsType="MandatorySingle" />
</CarouselView.ItemsLayout>
<CarouselView.ItemTemplate>
<grial:IntMemberTemplateSelector MemberName="Position">
<grial:IntMemberTemplateSelector.Items>
<!-- CAROUSEL'S PAGE 0 -->
<grial:IntMemberTemplateSelectorItem Value="0">
<DataTemplate>
.. more elements omitted
</DataTemplate>
</grial:IntMemberTemplateSelectorItem>
<!-- CAROUSEL'S PAGE 1 -->
<grial:IntMemberTemplateSelectorItem Value="1">
<DataTemplate>
.. more elements omitted
</DataTemplate>
</grial:IntMemberTemplateSelectorItem>
</grial:IntMemberTemplateSelector.Items>
</grial:IntMemberTemplateSelector>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentView.Content>
</ContentView>
The ViewModel
namespace PCS2.APP.ViewModels
{
public class ActivityOverviewViewModel : ObservableObject
{
private List<ActivityLocation> activityData;
private readonly IRoutingService _routingService;
private double _screenOpacity;
private bool _showLoadingAnimation;
private int? _clientId;
private int _position;
private int _innerCarouselPosition;
// Position of the Parent page CarouselView
public int Position
{
get { return _position; }
set { SetProperty(ref _position, value); }
}
// Data source for the child data
public List<ActivityLocation> ActivityData
{
get { return activityData; }
set { SetProperty(ref activityData, value); }
}
public double ScreenOpacity
{
get { return _screenOpacity; }
set { SetProperty(ref _screenOpacity, value); }
}
public bool ShowLoadingAnimation
{
get { return _showLoadingAnimation; }
set { SetProperty(ref _showLoadingAnimation, value); }
}
public ActivityOverviewViewModel(int? clientId = null, IRoutingService routingService = null)
: base(listenCultureChanges: true)
{
_clientId = clientId;
_routingService = routingService ?? Locator.Current.GetService<IRoutingService>();
LoadData();
}
private async void LoadData()
{
try
{
ShowLoadingAnimation = true;
ScreenOpacity = 0.1;
// Getting the data
var _activitiesData = await App.Database.GetActivityDataAsync(_clientId, DateTime.UtcNow);
ActivityData = _activitiesData;
}
catch (Exception ex)
{
throw;
}
finally
{
ShowLoadingAnimation = false;
ScreenOpacity = 1.0;
}
}
}
}
I have a multi selection listbox where only certain combinations of items are permitted in selection.
How can I deny certain selection?
The listbox is bound to view model. I thought I could do it in property setter but is doesn't work.
XAML
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems, ElementName=TranslatorsListView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In View Model
public System.Collections.IList SelectedItems
{
get
{
return SelectedModels;
}
set
{
//what here?
}
}
Edit:
I should have made clear, that I'm using multi selection. That's why I'm using interaction triggers and not just binding to SelectedItem. The linked answer is for single selection.
I've found that the easiest way was to not use SelectionMode nor SelectedItem, but instead utilize the Tapped event. From this you can get the "selected item".
I then set a bound property in the ViewModel that is used by the View to change the background of the selected item in the ListView if its one that I want to select. To do this effectively your ListView needs to be tied to an ObservableCollection of ViewModels (or a class that implements INotifyPropertyChanged).
Here's some rough code to illustrate what I mean:
In Xaml View code
<ListView
ItemsSource ="{Binding Values}"
ItemTapped="OnItemTapped"
HasUnevenRows="True"
RefreshAllowed="True"
SelectionMode="None"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid RowSpacing="0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<BoxView Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" BackgroundColor="{StaticResource Blue}" IsVisible="{Binding IsSelected}" />
<Label Grid.Column="0" Grid.Row="0" HorizontalTextAlignment="Center" VerticalOptions="Center" Text="{Binding Destination}"/>
<Label Grid.Column="1" Grid.Row="0" HorizontalTextAlignment="Start" VerticalOptions="Center" Text="{Binding Value}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In code behind:
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
var param = (ValueViewModel)e.Item;
if (((SourceViewModel)BindingContext).SelectValueCommand.CanExecute(param))
{
((SourceViewModel)BindingContext).SelectValueCommand.Execute(param);
}
}
Then these classes used - the first is the BindingContext for the View
public class SourceViewModel:INotifyPropertyChanged
{
public SourceViewModel (List<ValueViewModel> list)
{
SelectValueCommand = new Command<ValueViewModel>((value) => ExecuteSelectValueCommand(value), (value)=>!IsBusy);
}
private ObservableCollection<ValueViewModel> _values = new ObservableCollection<ValueViewModel>();
private bool _isSelected;
private sring _description;
public ICommand SelectValueCommand;
public bool IsBusy{get;set;}
public ObservableCollection<ValueViewModel> Values
{
get=>_values;
set
{
_values = value;
OnPropertyChanged(nameof(Values));
}
}
public void ExecuteSelectValueCommand(ValueViewModel value)
{
if(IsBusy) return;
item.IsSelected = CanValueBeSelected(value);
}
private bool CanValueBeSelected(ValueViewModel value)
{
var result = false;
//Some logic to determine if it can be selected
return result;
}
#region INotifyPropertyChanged implementation
// ...
#endregion
}
public class ValueViewModel:INotifyPropertyChanged
{
public ValueViewModel (string description, int value)
{
Description = description;
Value = value;
}
private int _value;
private bool _isSelected;
private sring _description;
public int Value
{
get=>_value;
set
{
_value=value;
OnPropertyChanged(nameof(Value));
}
}
public bool IsSelected
{
get=> _isSelected;
set
{
_isSelected= value;
OnPropertyChanged(nameof(IsSelected));
}
}
public string Description
{
get=> _description;
set
{
_description= value;
OnPropertyChanged(nameof(Description));
}
}
#region INotifyPropertyChanged implementation
// ...
#endregion
}
I am using Material Design in XAML Toolkit. I have the main window with drawer, which contains the list of user controls (app tabs). When I click on them - application tab switches between this controls. I want to add a button to the window, and when I click on it I want to switch between tabs too. You can see important parts of my code here:
<materialDesign:DialogHost Identifier="RootDialog" SnackbarMessageQueue="{Binding ElementName=MainSnackbar, Path=MessageQueue}">
<materialDesign:DrawerHost IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
<materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel MinWidth="212">
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}"
DockPanel.Dock="Top"
HorizontalAlignment="Right" Margin="16"
IsChecked="{Binding ElementName=MenuToggleButton, Path=IsChecked, Mode=TwoWay}" />
<ListBox x:Name="DemoItemsListBox" Margin="0 16 0 16" SelectedIndex="0"
ItemsSource="{Binding DemoItems}"
PreviewMouseLeftButtonUp="UIElement_OnPreviewMouseLeftButtonUp">
<ListBox.ItemTemplate>
<DataTemplate DataType="helpers:DemoItem">
<TextBlock Text="{Binding Name}" Margin="32 0 32 0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel>
<materialDesign:ColorZone Padding="16" materialDesign:ShadowAssist.ShadowDepth="Depth2"
Mode="PrimaryDark" DockPanel.Dock="Top">
<DockPanel>
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}" IsChecked="False"
x:Name="MenuToggleButton"/>
<materialDesign:PopupBox x:Name="popupBox">
<TextBlock>Check me please</TextBlock>
</materialDesign:PopupBox>
<CheckBox x:Name="LizenzeCheckBox" DockPanel.Dock="Right" Style="{StaticResource MaterialDesignCheckBox}" Tag="False">
<CheckBox.IsChecked>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}">
<Binding.ValidationRules>
<helpers:IsCheckedValidationRule />
</Binding.ValidationRules>
</Binding>
</CheckBox.IsChecked>CheckBox text</CheckBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="22">My App</TextBlock>
</DockPanel>
</materialDesign:ColorZone>
<Button x:Name="TheBUTTON" Click="Button_Click">Ckicc</Button>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="1"
HorizontalScrollBarVisibility="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.HorizontalScrollBarVisibilityRequirement}"
VerticalScrollBarVisibility="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.VerticalScrollBarVisibilityRequirement}"
Padding="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.MarginRequirement}">
<ContentControl Content="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.Content}" />
</ScrollViewer></Grid>
This is my main window xaml code, as you can see, I bind ListBox Values to DemoItem[] array from viewModel. "TheButton" onclick event is the event which I want to use for tab switching.
My main window view model is:
public class MainWindowViewModel
{
public DemoItem[] DemoItems { get; }
public MainWindowViewModel(ISnackbarMessageQueue snackbarMessageQueue)
{
if (snackbarMessageQueue == null) throw new ArgumentNullException(nameof(snackbarMessageQueue));
DemoItems = new[]
{
new DemoItem("Tab1", new Tab1()),
new DemoItem("Tab2", new Tab2()),
new DemoItem("Tab3", new Tab3()),
};
}
}
The MainWindow.cs is:
public partial class MainWindow : Window
{
public static Snackbar Snackbar;
public MainWindow()
{
InitializeComponent();
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
}).ContinueWith(t =>
{
MainSnackbar.MessageQueue.Enqueue("Welcome to my app");
}, TaskScheduler.FromCurrentSynchronizationContext());
DataContext = new MainWindowViewModel(MainSnackbar.MessageQueue);
}
private void UIElement_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
//until we had a StaysOpen glag to Drawer, this will help with scroll bars
var dependencyObject = Mouse.Captured as DependencyObject;
while (dependencyObject != null)
{
if (dependencyObject is ScrollBar) return;
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
}
MenuToggleButton.IsChecked = false;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//what to do here?
}
}
The DemoItem Class is:
public class DemoItem : INotifyPropertyChanged
{
private string _name;
private object _content;
private ScrollBarVisibility _horizontalScrollBarVisibilityRequirement;
private ScrollBarVisibility _verticalScrollBarVisibilityRequirement;
private Thickness _marginRequirement = new Thickness(16);
public DemoItem(string name, object content)
{
_name = name;
Content = content;
}
public string Name
{
get { return _name; }
set { this.MutateVerbose(ref _name, value, RaisePropertyChanged()); }
}
public object Content
{
get { return _content; }
set { this.MutateVerbose(ref _content, value, RaisePropertyChanged()); }
}
public ScrollBarVisibility HorizontalScrollBarVisibilityRequirement
{
get { return _horizontalScrollBarVisibilityRequirement; }
set { this.MutateVerbose(ref _horizontalScrollBarVisibilityRequirement, value, RaisePropertyChanged()); }
}
public ScrollBarVisibility VerticalScrollBarVisibilityRequirement
{
get { return _verticalScrollBarVisibilityRequirement; }
set { this.MutateVerbose(ref _verticalScrollBarVisibilityRequirement, value, RaisePropertyChanged()); }
}
public Thickness MarginRequirement
{
get { return _marginRequirement; }
set { this.MutateVerbose(ref _marginRequirement, value, RaisePropertyChanged()); }
}
public event PropertyChangedEventHandler PropertyChanged;
private Action<PropertyChangedEventArgs> RaisePropertyChanged()
{
return args => PropertyChanged?.Invoke(this, args);
}
}
My MutateVerbose function looks like:
public static void MutateVerbose<TField>(this INotifyPropertyChanged instance, ref TField field, TField newValue, Action<PropertyChangedEventArgs> raise, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<TField>.Default.Equals(field, newValue)) return;
field = newValue;
raise?.Invoke(new PropertyChangedEventArgs(propertyName));
}
I don't know how to switch tabs with button click in this situation. Help me, please!
I have the following user control:
The Xaml:
<UserControl x:Class="ScreenRecorder.TimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="27" Width="176">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBox Width="150" Height="25" Text="{Binding Time}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<StackPanel Orientation="Vertical">
<Button Width="25" Height="12.5" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btnKeyUp_Clicked">
<Image Source="up.png" Height="10" Width="10" VerticalAlignment="Top"/>
</Button>
<Button Width="25" Height="12.5" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btnKeyDown_Clicked">
<Image Source="down.png" Height="10" Width="10" VerticalAlignment="Top"/>
</Button>
</StackPanel>
</StackPanel>
</Grid>
The Code:
public partial class TimePicker : UserControl, INotifyPropertyChanged
{
public TimePicker()
{
InitializeComponent();
this.DataContext = this;
//Time = m_time;
}
public static DependencyProperty TimeProperty = DependencyProperty.Register(
"Time", typeof(string), typeof(TimePicker));
//private string m_time = DateTime.Now.ToString();
public string Time
{
get { return (string)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
NotifyPropertyChanged("Time");
}
}
private void btnKeyUp_Clicked(object sender, RoutedEventArgs e)
{
DateTime curTime = Convert.ToDateTime(Time);
curTime += new TimeSpan(0, 0, 1);
Time = curTime.ToString();
}
private void btnKeyDown_Clicked(object sender, RoutedEventArgs e)
{
DateTime curTime = Convert.ToDateTime(Time);
curTime -= new TimeSpan(0, 0, 1);
Time = curTime.ToString();
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
And I have another user control that uses this user control as follow:
<StackPanel>
<Label Content="Begin Record Time" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5"/>
<local:TimePicker Time="{Binding StartRecordTime}"/>
</StackPanel>
StartRecordTime looks like this:
public string StartRecordTime
{
get { return m_startRecord; }
set
{
m_startRecord = value;
NotifyPropertyChanged("StartRecordTime");
}
}
I want to change the StartRecordTime according to the Time Property and vice versa, but only the Time property is changing.
Thank you.
Try this:
<local:TimePicker Time="{Binding Path=StartRecordTime, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
Your binding won't work as you've set the DataContext of TimePicker to TimePicker itself. You'll notice a binding error in the Output window:
BindingExpression path error: 'StartRecordTime' property not found on 'object' ''TimePicker' (Name='')'. BindingExpression:Path=StartRecordTime; DataItem='TimePicker' (Name=''); target element is 'TimePicker' (Name=''); target property is 'Time' (type 'String')
I'd suggest to have a more 'sane' experience you remove DataContext = this from the TimePicker constructor and set the Grid's DataContext to the TimePicker by Element Name. Add a name attribute to the UserControl element:
<UserControl x:Name="Root" ...
And set the DataContext of the Grid:
<Grid DataContext="{Binding ElementName=Root}">
This will be inherited by all child elements. You will also need to change your binding to TwoWay, either explicitly:
<local:TimePicker Time="{Binding Path=StartRecordTime, Mode=TwoWay}" />
Or by setting this as the default in the DependencyProperty registration:
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
"Time", typeof(string), typeof(TimePicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I'd also note that there's no reason for TimePicker to implement INotifyPropertyChanged. I suggest you remove this.
I have made a user control called Section which has 2 custom DependencyProperty: Title and Elements (kinda like content). I have used Section on several pages it displays just fine on every page.
The following xaml code is a part of my AccountView class
<Controls:Section Title="Epic Movies" Grid.Column="1" Grid.Row="1" x:Name="DescriptionSection" Visibility="Collapsed">
<Controls:Section.Elements>
<StackPanel>
<TextBlock Style="{StaticResource FrameLabel}" Text="Description:" Grid.Column="1"/>
<TextBlock x:Name="DescriptionField" Style="{StaticResource FrameText}" Text="some text..." Grid.Column="1" Grid.Row="1"/>
<TextBlock Text="Products:" Style="{StaticResource FrameLabel}"/>
<ScrollViewer Margin="12,0" VerticalScrollBarVisibility="Auto" MaxHeight="180">
<StackPanel x:Name="ProductList"/>
</ScrollViewer>
<TextBlock Style="{StaticResource FrameLabel}" Text="Category"/>
<TextBlock x:Name="CategoryField" Style="{StaticResource FrameText}" Text="some category"/>
<TextBlock Style="{StaticResource FrameLabel}" Text="Views:"/>
<TextBlock x:Name="ViewsField" Style="{StaticResource FrameText}" Text="12322 Views"/>
<TextBlock Style="{StaticResource FrameLabel}" Text="Price:"/>
<TextBlock x:Name="PriceField" Style="{StaticResource FrameText}" Text="455$"/>
<Button Content="Go to Module" Grid.Column="1" FontSize="15" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="20,15"/>
</StackPanel>
</Controls:Section.Elements>
</Controls:Section>
When i try set the text of my TextBlocks in c# code all the TextBlock are null
private void ShowModuleInformation(Module m)
{
DescriptionSection.Title = m.Name;
//DescriptionField is null even though clearly defined in xaml
DescriptionField.Text = m.Description;
PriceField.Text = m.Price + "";
ViewsField.Text = m.views+"";
CategoryField.Text = m.Category.Name;
foreach (Product p in m.Products)
{
TextBlock t = new TextBlock() { Text = p.Name };
ProductList.Children.Add(t);
}
DescriptionSection.Visibility = Visibility.Visible;
}
My Section c# class looks as follow:
public partial class Section : UserControl
{
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
"Title", typeof(string), typeof(Section), new PropertyMetadata(new PropertyChangedCallback(TitleChanged)));
public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register(
"Elements", typeof(UIElement), typeof(Section), new PropertyMetadata(new PropertyChangedCallback(ElementsChanged)));
public Section()
{
InitializeComponent();
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public UIElement Elements
{
get { return (UIElement)GetValue(ElementsProperty); }
set { SetValue(ElementsProperty, value); }
}
private static void TitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sec = (Section)d;
if (sec != null)
{
sec.TitleField.Text = (string)e.NewValue;
}
}
private static void ElementsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Section sec = (Section)d;
if (sec != null)
{
sec.ElementPanel.Content = (UIElement)e.NewValue;
}
}
I have solved this with a little help from this thread. All i needed to do was to make Section derive ContentControl instead of User Control