Currently I am playing around with .Net Maui but I maybe it's the same behavior as Xamarin.
I've created a simple Search-Control which is based on a ContentView.
ObjectSearchControl.xaml
<ContentView
x:Class="DeepBlue.Controls.ObjectSearchControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converter="clr-namespace:DeepBlue.Converter"
xmlns:selector="clr-namespace:DeepBlue.Helpers"
x:Name="MySearchControl">
<StackLayout
Orientation="Vertical"
VerticalOptions="FillAndExpand">
<SearchBar
x:Name="ObjectSearchBar"
IsSpellCheckEnabled="False"
Keyboard="Text"
Placeholder="{Binding SearchBarPlaceholderText}"
TextChanged="ObjectSearchBar_TextChanged" />
<CollectionView
x:Name="ObjectResultView"
HeightRequest="500"
ItemsSource="{Binding DataSource}"
SelectionChanged="ObjectResultView_SelectionChanged">
</CollectionView>
</StackLayout>
</ContentView>
ObjectSearchControl.xaml.cs
public partial class ObjectSearchControl : ContentView
{
public static readonly BindableProperty SearchBarPlaceholderTextProperty
= BindableProperty.Create(nameof(SearchBarPlaceholderText), typeof(string),
typeof(ObjectSearchControl), string.Empty);
public static readonly BindableProperty DataSourceProperty
= BindableProperty.Create(nameof(DataSource), typeof(object),
typeof(ObjectSearchControl));
public ObjectSearchControl()
{
InitializeComponent();
Content.BindingContext = this;
}
public string SearchBarPlaceholderText
{
get => (string)GetValue(SearchBarPlaceholderTextProperty);
set => SetValue(SearchBarPlaceholderTextProperty, value);
}
public object DataSource
{
get => (object)GetValue(DataSourceProperty);
set => SetValue(DataSourceProperty, value);
}
}
This ContentView I've inserted in my Page
<StackLayout
x:Name="SelectFishingSection"
HeightRequest="600"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
VerticalOptions="FillAndExpand">
<controls:ObjectSearchControl
DataSource="{Binding NonFilterdDataSource}"
HeightRequest="550"
HorizontalOptions="FillAndExpand"
SearchBarPlaceholderText="Placeholder"
VerticalOptions="FillAndExpand" />
</StackLayout>
After running the code the control rendered and the Placeholdertext in the SearchBar is set correct. So I thought the implementation of the binding is correct. But in my CollectionView no element is rendered.
So I debugged a lot and found out that the BindingContext is set 2 times. When I initialize the control all the properties have got NULL values. -> seems okay
Then the control is appearing and I get the elements from DB and set them to "DataSource".
<ContentPage.Behaviors>
<mctBehaviors:EventToCommandBehavior Command="{Binding SetDataSourcesCommand}" EventName="Appearing" />
</ContentPage.Behaviors>
private async Task SetDataSources()
{
try
{
IsBusy = true;
NonFilterdDataSource = new ObservableCollection<MyTestModel>(await DataService.GetAll());
}
finally
{
IsBusy = false;
}
}
That's also called and seems correct. After that BindingContext is set (OnBindingContextChanged is called in my ObjectSearchControl.xaml.cs) and all properties (SearchBarPlaceholderText and DataSource) have got correct values. At this point in DataSource there are 9 Elements!
If I continue debugging the DataSource is set to NULL and also the BindingContext is set to NULL! But I don't understand why?
Output window in VS shows only "External Code" and I can not figure out why this is happening.
I found a few similar questions but none of the could solve my problem.
After analysing the "External Code" in VS I found out that the source of the problem must be somewhere in the measurement for the control. So I removed VerticalOptions="FillAndExpand" from my controls:ObjectSearchControl implemention and after that the problem was gone. BindingContext is set only one time and everything is working as expected!
Related
So, I have a custom control, CusConA, that works basically like a textbox - you type amount of money that you need, and I have a button below, whom by getting clicked saves that amount(from CusConA) somewhere, and that is working fine.
But I want to try the same functionality basically by clicking anywhere on that page (something like OnBlur in asp.net), or to be precise, when my CusConA is not in focus anymore.
By doing what is shown with the --> in code, I achieved sort of a solution, this way when pressing anywhere, even if I never even tried to write an amount, the command is being executed.
So, to try to circle my question, I need this command to execute only after typing some amount, and clicking somewhere alse after. How can I do that?
<Frame
Margin="55,0"
Padding="0"
BorderColor="Blue"
CornerRadius="30">
<StackLayout Orientation="Horizontal">
<Label
Margin="10"
FontAttributes="Bold"
FontSize="20"
HorizontalTextAlignment="Center"
Text="RSD"
TextColor="Some text"
VerticalTextAlignment="Center" />
<customControls:CusConA
Margin="0,0,15,0"
HorizontalOptions="FillAndExpand"
Keyboard="Numeric"
Placeholder="0,00"
PlaceholderColor="Gray"
Text="Some text"
TextColor="Black" >
--> <customControls:CusConA.Behaviors>
<xct:EventToCommandBehavior EventName="Unfocused" Command="{Binding DoSomething}" ></xct:EventToCommandBehavior>
</customControls:CusConA.Behaviors>
</customControls:CusConA>
</StackLayout>
</Frame>
Can you change DoSomething to check whether the amount has been typed? Might involve adding a boolean property to your control:
bool CanExecute { get; set; }
Then have "amount" bound to a property whose setter sets CanExecute = true; or CanExecute = false;, depending on whether an amount has been typed. Something like:
string Amount
{
...
set {
_amount = value;
myControl.CanExecute = value.Count > 0;
}
}
Then change DoSomething body to
if (this.CanExecute) { ... }
Alternatively, other techniques can be used to have a change to Amount trigger a change to a property on myControl.
The essential points are:
Adding CanExecute property, so control can be told when it is valid to execute that command.
Using some technique to bind or trigger myControl.CanExecute change, from elsewhere.
I think you can use EventToCommandBehavior to achieve this function.
There is an example of an EventToCommandBehavior in the Xamarin.Forms samples (see here).
<ContentPage.BindingContext>
<focusapp:MyViewModel></focusapp:MyViewModel>
</ContentPage.BindingContext>
<StackLayout>
<Entry>
<Entry.Behaviors>
<Behaviors:EventToCommandBehavior
EventName="Unfocused"
Command="{Binding EntryUnfocused}" />
</Entry.Behaviors>
</Entry>
</StackLayout>
And define EntryUnfocused in your viewmodel.cs (e.g. MyViewModel) just as follows:
MyViewModel.cs
public class MyViewModel
{
public ICommand EntryUnfocused { get; protected set; }
public MyViewModel() {
EntryUnfocused = new Command(CompletedCommandExecutedAsync);
}
private void CompletedCommandExecutedAsync(object param)
{
System.Diagnostics.Debug.WriteLine("------------> come here....");
}
}
I want to access the properties created in ViewModel to the xaml code behind file. Please have a look at the attached screenshot for better understanding on my question.
Please Click Here to View the Screenshot of my Xaml code
Click Here for the Properties code
I have bind the "EntryText" property to an Entry field and "LblText" property to a Label. So, now I just want to transfer the value of Entry to the Label on a button click event.
You're on the right track, just need to search slightly differently.
There's multiple ways of doing this. I will tell you the simplest way since that's also suggested in the Xamarin Official Docs. So your Xaml code will look like this
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ButtonDemos.BasicButtonClickPage"
Title="Basic Button Click">
<StackLayout>
<Label x:Name="label"
Text="Click the Button below"
FontSize="Large"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center" />
<Button Text="Click to Rotate Text!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Clicked="OnButtonClicked" />
</StackLayout>
</ContentPage>
And your C# file would look like this
public partial class BasicButtonClickPage : ContentPage
{
public BasicButtonClickPage ()
{
InitializeComponent ();
}
async void OnButtonClicked(object sender, EventArgs args)
{
await label.RelRotateTo(360, 1000);
}
}
You can use code-behind to invoke a method in the view model. So in that method, you can change the LblText. Refer below code.
<Button x:Name="btn1" Clicked="btnClicked" />
In code-behide
private void btnClicked(object sender, EventArgs e){
_viewModel.ChangeLabelText();
}
In the View Model
public void ChangeLabelText() {
LblText = EntryText;
}
You can use the page's BindingContext and cast it to your model. And, then access the property from there.
var myModel = this.BindingContext as MainPageProperties;
if(myModel!=null)
{
//Access your property here!
var text = myModel.LblText;
}
I have a Popup which will fill the whole page when opened.
<Grid x:Name="gridRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="Open" HorizontalAlignment="Center" Click="{x:Bind viewModel.OpenPopup}" />
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsOpen}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:MyPopup Width="{Binding ElementName=gridRoot, Path=ActualWidth}" Height="{Binding ElementName=gridRoot, Path=ActualHeight}"/>
</Popup>
</Grid>
The Popup is a UserControl
<Grid Background="Red">
<Button Content="Close" HorizontalAlignment="Center" Click="{x:Bind viewModel.ClosePopup}" />
</Grid>
The page
When popup is shown
Close the popup, resize the page, then reopen the popup. Notice that it does not match the new size of container page even though its Width and Height is bound to gridRoot . Do I have to manually set a new Width and Height for the popup? Why can't I achieve this with binding? This issue also appears on mobile during 'OrientationChanged'
Based on Decade Moon comment, this is how to resize the popup to match the parent container as its size changed.
Create a dependency property in the code behind
public double PageWidth
{
get { return (double)GetValue(PageWidthProperty); }
set { SetValue(PageWidthProperty, value); }
}
public static readonly DependencyProperty PageWidthProperty =
DependencyProperty.Register("PageWidth", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
public double PageHeight
{
get { return (double)GetValue(PageHeightProperty); }
set { SetValue(PageHeightProperty, value); }
}
public static readonly DependencyProperty PageHeightProperty =
DependencyProperty.Register("PageHeight", typeof(double), typeof(GamePage), new PropertyMetadata(0d));
Update the value on SizeChanged event
private void GamePage_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width > 0d && e.NewSize.Height > 0d)
{
PageWidth = e.NewSize.Width;
PageHeight = e.NewSize.Height;
}
}
Then in XAML, just use x:Bind to bind the popup width and height
<Popup x:Name="popupCorrect" VerticalAlignment="Top" IsOpen="{Binding IsPopupCorrectOpen, Mode=TwoWay}" IsLightDismissEnabled="False">
<Popup.ChildTransitions>
<TransitionCollection>
<PaneThemeTransition Edge="Left" />
</TransitionCollection>
</Popup.ChildTransitions>
<uc:PopupCorrect Width="{x:Bind PageWidth, Mode=TwoWay}" Height="{x:Bind PageHeight, Mode=TwoWay}"/>
</Popup>
Pretty straight forward. Just remember not to use the ActualWidth or ActualHeight properties for binding source as they do not raise the PropertyChanged event.
Although it has an ActualWidthProperty backing field, ActualWidth does not raise property change notifications and it should be thought of as a regular CLR property and not a dependency property.
For purposes of ElementName binding, ActualWidth does not post updates when it changes (due to its asynchronous and run-time calculated nature). Do not attempt to use ActualWidth as a binding source for an ElementName binding. If you have a scenario that requires updates based on ActualWidth, use a SizeChanged handler.
#PutraKg have a great way.
But I have two way to solve it.
The first is set the VerticalAlignment="Center" HorizontalAlignment="Center" that can make the popup in the center.
But I think youare not content to put it in the center.
The great way is use the screen position.
You can get the Grid's screen postion and make it to popup.
In open button
private void Button_OnClick(object sender, RoutedEventArgs e)
{
var grid = (UIElement)popupCorrect.Parent; //get grid
var p = grid.TransformToVisual (Window.Current.Content).TransformPoint(new Point(0, 0)); //get point
popupCorrect.HorizontalOffset = p.X;
popupCorrect.VerticalOffset = p.Y;
popupCorrect.IsOpen = !popupCorrect.IsOpen;
}
I'm having an issue using the very basis of CocosSharp - rendering with Xamarin.Forms.
Having CocosSharp in version 1.7.1 and Xamarin 2.3.2.127.
ViewCreated event is not called at all for my CocosSharpView (either created from code, or from xaml). What is more, direct casting to CCGameView throws compilation error:
Error CS0039: Cannot convert type 'CocosSharp.CocosSharpView' to 'CocosSharp.CCGameView' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion
Furthermore, I replaced direct element cast to casting in a CocosSharpView ViewCreated event:
private void HandleViewCreated(object sender, EventArgs e)
{
var gameView = sender as CCGameView;
if (gameView == null) return;
(...)
}
However, the event is never called, the view is never rendered. My xaml file looks as follows:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cf="clr-namespace:CocosSharp;assembly=CocosSharp.Forms"
x:Class="LocationTeacher.MainPage">
<Grid x:Name="MainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<cf:CocosSharpView x:Name="CocosView"
Grid.Row="0"
ResolutionPolicy="ShowAll"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="Transparent" />
<StackLayout Grid.Row="1">
<Button Text="Move Circle Left" />
<Button Text="Move Circle Right" />
</StackLayout>
</Grid>
</ContentPage>
and my code behind:
public partial class MainPage : ContentPage
{
private GameScene gameScene;
public MainPage()
{
InitializeComponent();
CocosView.ViewCreated += HandleViewCreated;
}
private void HandleViewCreated(object sender, EventArgs e)
{
var gameView = sender as CCGameView;
if (gameView == null) return;
(...)
}
}
Anyone encountered the same issue? (And managed to resolve it, if so - then how?)
EDIT: The solution was quite straightforward indeed... It's stupid to say this, but apparently I did not enable hardware acceleration (use of host GPU) in my emulator, hance the rendering did not occur at all... When enabled everything seems to work properly.
Sorry for the confusion, however it could prove helpful if anyone encounters similar issue.
I have been messing with something that works in the code behind but when I try and bind to a MVVM , nothing displays. First I will show the code behind, then MVVM ( same xaml ). I want to use MVVM and not code behind.
Code Behind (works):
var loadOp = ctx.Load<GateBlox.Web.Models.Structure>(ctx.GetStructuresQuery());
loadOp.Completed += (s, e) => { _treeView.ItemsSource = loadOp.Entities.Where(struc => !struc.StructureParentFK.HasValue); };
XAML
<Grid x:Name="LayoutRoot">
<sdk:TreeView x:Name='_treeView' DataContext='{StaticResource ViewModel}'>
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource='{Binding Children}'>
<TextBlock Text='{Binding StructureName}' />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
</Grid>
MVVM (doesnt bind)
private LoadOperation<Structure> _loadStructures;
private StructureContext _structureContext;
private IEnumerable<Structure> _structures;
public IEnumerable<Structure> Structures
{
get { return this._structures; }
set { this._structures = value; RaisePropertyChanged("Structures"); }
}
public StructuresViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
_structureContext = new StructureContext();
_loadStructures = _structureContext.Load(_structureContext.GetStructuresQuery().Where (p=> ! p.StructureParentFK.HasValue));
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
this.Structures = _loadStructures.Entities;
}
Have your checked that you are not getting a binding expression error in the output? You are binding the items source of the data template to a property named Children, but your view model exposes a data source named Structures.
Also, in your working example, you are setting the ItemsSource of the TreeView, but in your MVVM XAML you are setting the ItemsSource of your data template. Is there an inconsistency between what ItemsSource you need to set/bind to?
You might also consider using a collection data source that implements the INotifyCollectionChanged interface (ObservableCollection or expose the binding source as a ICollectionView that uses a PagedCollectionView).
I recommend you take a look at this information about data binding in MVVM, as it provides excellent guidance on setting up data sources in your view models.
You are not setting the ItemsSource for your TreeView. I think your xaml should look something like this:
<Grid x:Name="LayoutRoot">
<sdk:TreeView x:Name='_treeView' DataContext='{StaticResource ViewModel}'
ItemsSource="{Binding Structures}">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource='{Binding Children}'>
<TextBlock Text='{Binding StructureName}' />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
</Grid>
Hope this helps :)
I almost have it working now. I took a different approach and went with a HeirarchicalDataTemplate. At the moment the data is showing but not correctly: The child1 record is shwoing up as a parent as well.
Parent1(level1)
Parent2(level1)
Child1(level2)
Child1(level1)
<navigation:Page x:Class="GateBlox.Views.Structure"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640"
d:DesignHeight="480"
Title="Structure Page"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:viewmodel="clr-namespace:GateBlox.ViewModels">
<UserControl.Resources>
<viewmodel:StructuresViewModel x:Key='ViewModel'>
</viewmodel:StructuresViewModel>
</UserControl.Resources>
<Grid x:Name="LayoutRoot"
DataContext='{StaticResource ViewModel}'>
<Grid.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate"
ItemsSource="{Binding Path=Parent}">
<TextBlock FontStyle="Italic"
Text="{Binding Path=StructureName}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=StructureName}"
FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</Grid.Resources>
<sdk:TreeView x:Name='treeView'
Width='400'
Height='300'
ItemsSource='{Binding Structures}'
ItemTemplate='{StaticResource NameTemplate}'>
</sdk:TreeView>
</Grid>
using System;
using System.Collections.ObjectModel;
using GateBlox.Web.Models;
using System.ServiceModel.DomainServices.Client;
using GateBlox.Web.Services;
using GateBlox.Helpers;
using System.ComponentModel;
using System.Collections.Generic;
namespace GateBlox.ViewModels
{
public class StructuresViewModel : ViewModelBase
{
private LoadOperation<Structure> _loadStructures;
private StructureContext _structureContext;
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return this._structures; }
set { this._structures = value; RaisePropertyChanged("Structures"); }
}
public StructuresViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
_structureContext = new StructureContext();
_loadStructures = _structureContext.Load(_structureContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
this.Structures = IEnumerableConverter.ToObservableCollection(_loadStructures.Entities);
}
}
}