I have used event to command behaviours on an entry field to trigger a command that disables another on screen entry field when the users starts typing in the current entry field.
To be clear the event to command behaviour is working fine and so is the binding, meaning that I have put debug points on both and they are hit when the code is executed. The problem is that even though they are hit the IsEnabled property does not change on the other entry field.
The code:
The Xaml:
<Entry
PlaceholderColor="Black"
TextColor="Black"
Text="{Binding Identity, Mode=OneWayToSource}"
Placeholder="Driver ID"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1">
<Entry.Behaviors>
<behaviors:FormEventToCommandBehavior EventName="TextChanged" Command="{Binding TextEntryTrigger}"/>
</Entry.Behaviors>
</Entry>
<Label Text="OR" TextColor="Black"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="2"
VerticalOptions="Center"
/>
<Entry
PlaceholderColor="Black"
TextColor="Black"
Text="{Binding Identity, Mode=OneWayToSource}"
Placeholder="Route ID"
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="3"
IsEnabled="{Binding RouteIDVis}"/>
The ViewModel:
These command and properties are set in the right place I have just put them here for brevity.
public ICommand TextEntryTrigger { get; protected set; }
TextEntryTrigger = new Command(() => HandleEntryFieldVisibility());
public bool RouteIDVis
{
get { return _routeidvis; }
set
{
_routeidvis = value;
OnPropertyChanged(nameof(RouteIDVis));
}
}
private void HandleEntryFieldVisibility()
{
RouteIDVis = false;
}
I have left out the code for the event to command because as I stated when putting debug points in all of this they all get hit and I can see the RouteIDVis is being set to false but it does not disable the second entry field.
I have looked around but I cant seem to find a straight answer just some one sentence answers and no proper code answers.
Any ideas?
Have you tried using the solution from
https://forums.xamarin.com/discussion/71527/problem-with-binding-isenabled-property-of-an-entry
It make use of 2 Entry controls. The first Entry control is disabled while the second Entry control is enabled. The IsVisible ensures only one control is visible.
<ContentView Grid.Row="0"
Grid.Column="1"
IsVisible="{Binding EntryEnabled}">
<Entry IsEnabled="True"
Text="{Binding PropertyValue, Mode=TwoWay}"/>
</ContentView>
<ContentView Grid.Row="0"
Grid.Column="1"
IsVisible="{Binding EntryEnabled, Converter={StaticResource InverseConverter}}">
<Entry IsEnabled="False"
Text="{Binding PropertyValue, Mode=TwoWay}"/>
</ContentView>
<Entry Text="{Binding PropertyValue, Mode=TwoWay}"
IsEnabled="False"/>
Change order of property, that may be a problem. It happened to me when using IsEnabled with button.
Related
I'm making an app where I have layouts generate dynamically based on a list.
<StackLayout>
<CollectionView x:Name="taskList">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Task">
<VerticalStackLayout Margin="15">
<Entry Text="{Binding name}" IsReadOnly="True" />
<Entry Text="{Binding departmentsString}" IsReadOnly="True"/>
<HorizontalStackLayout>
<Entry Text="{Binding status}" IsReadOnly="True"/>
<Entry Text="{Binding deadline}" IsReadOnly="True" />
<Entry Text="{Binding author.fullName}" IsReadOnly="True"/>
</HorizontalStackLayout>
<Entry Text="{Binding description}" IsReadOnly="True" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
The list is bonded like this:
taskList.ItemsSource = company.tasks;
I want to refresh this whenever I add new items to the list.
I tried rebinding the list to the ItemsSource but it just didn't work:
taskList.ItemsSource = company.tasks;
How should I do it? Can I just refresh the view so it generates everything again?
Instead of assigning the ItemsSource from code-behind, you should bind a custom collection, which makes it much easier and decouples the UI and logic. Read more over the MVVM pattern.
(I don't have a IDE here, so it's not tested)
You could do something like:
// data class
public class TaskInfo
{
public string name {get; set;}
public string departmentsString {get;set;}
}
// you page/view whatever code behind the xaml
public class YourPage : // insert the class which it is derived from.
{
// create a collection property, which generates events when changed. The UI react on these events.
public ObservableCollection<TaskInfo> Tasks {get;} = new();
public YourPage()
{
InitComponentsOrSomething();
// assign the datacontext (which the view binds to)
this.DataContext = this;
}
}
And your xaml would be something like this: (mind the Binding instead of the name)
<StackLayout>
<CollectionView ItemsSource="{Binding Tasks}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Task">
<VerticalStackLayout Margin="15">
<Entry Text="{Binding name}" IsReadOnly="True" />
<Entry Text="{Binding departmentsString}" IsReadOnly="True"/>
<HorizontalStackLayout>
<Entry Text="{Binding status}" IsReadOnly="True"/>
<Entry Text="{Binding deadline}" IsReadOnly="True" />
<Entry Text="{Binding author.fullName}" IsReadOnly="True"/>
</HorizontalStackLayout>
<Entry Text="{Binding description}" IsReadOnly="True" />
</VerticalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
If you want to add items to the collection, just use the Tasks property:
Tasks.Add(new TaskInfo { name = "some name", departmentsString = "Enter here" });
ps: I would not call a data object 'Task', either give it a better description. TaskInfo/CompanyTask.
You should use an IEnumerable collection that sends property change notifications(such as ObservableCollection) for the ItemsSource, then simply add to the underlying collection.
Here's an example:
ObservableCollection<string> tasks = new();
taskList.ItemsSource = tasks;
tasks.Add("Do something");
Microsoft Reference
I have the following XAML...
<TabControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
Name="customerTab"
ItemsSource="{ Binding DetailViewModels }"
SelectedItem="{Binding SelectedDetailViewModel, Mode=TwoWay}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="*" Visibility="{Binding HasChanges, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Command="{Binding CloseCommand}" Style="{StaticResource closeButtonStyle}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Basically, I have a listview where I can click on a record to view detail. The detail record gets displayed in the tab control.
You can see I have a button which is bound to a command that closes the tab.
When I close the tab, the following binding error displays...
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TabControl', AncestorLevel='1''. BindingExpression:Path=TabStripPlacement; DataItem=null; target element is 'TabItem' (Name=''); target property is 'NoTarget' (type 'Object')
I am not totally sure of the issue. Does this mean that the detail viewmodels cannot climb back up the hierarchy to the tab control when it closes?
The actual application works as designed, I just want to address this error so it does not keep coming up every time I close a tab.
The CloseCommand is a delegate command. Here is that code along with the method that it runs.
public DelegateCommand CloseCommand { get; private set; }
CloseCommand = new DelegateCommand(OnClose);
public void OnClose()
{
OnTabClosed?.Invoke(InstanceId);
}
OnTabClosed is an action that closes the tab and the InstanceId is simply a GUID of the detail viewmodel.
I did search online and found a way to hide the message, but I am disinclined to do that for fear of hiding more legitimate binding errors.
How do I fix this? What is the best way to debug?
Edit
Here is the code that handles the closing of the tab item...
private void HandleTabClosed(Guid instanceId)
{
DetailViewModels.Remove(DetailViewModels.First(vm => vm.InstanceId == instanceId));
}
I just made the test, and I find no error, but I do it a bit differently, so hope will be good for you :
In XAML, I use a "Tag" for buttons (very useful when you use templates).
Taking your code, that would be something like that :
<TabControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"
Name="customerTab"
ItemsSource="{ Binding DetailViewModels }"
SelectedItem="{Binding SelectedDetailViewModel, Mode=TwoWay}"
TabStripPlacement="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="*" Visibility="{Binding HasChanges, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Content="X" Click="Button_Click" Tag="{Binding InstanceId}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
Then on my "OnClick" event I will look which Instance I may remove, then remove it :
private void Button_Click(object sender, RoutedEventArgs e)
{
Button item = (Button)sender;
long instanceId = (long)item.Tag;
GroupResults selectedGroup = this.MyFullList.FirstOrDefault(x => x.InstanceId == instanceId);
if(selectedGroup!=null)
{
this.MyFullList.Remove(selectedGroup);
}
}
It's not really the same way as you did, but it works fine. I always use Tags when I work on templates, and until now never got any problem.
I have two page elements (in the same view) with one dependent on the other. I need my entry to be enabled only if the checkbox is NOT selected. Can I do this without creating another property?
<Label Text="Simulate" />
<CheckBox IsChecked="{Binding Simulate}"/>
<Label Text="Route Path"/>
<Entry Text="{Binding RoutePath}" IsEnabled="{Binding Simulate}"/>
Use a value converter like InvertedBoolConverter from xamarin community toolkit package:
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
<Entry Text="{Binding RoutePath}"
IsEnabled="{Binding Simulate, Converter={xct:InvertedBoolConverter}}"/>
I have a listview in WPF which holds an observable collection of objects.
The objects are simple rectangular boxes containing a set of radio buttons:
The two sets of radio buttons are kept separate by setting the 'GroupName' in xaml to either GroupName="Numbers" or GroupName="Letters"
--EDIT--
As requested, here is the code within MyObject for the radiobuttons:
<RadioButton Grid.Column="1" Grid.Row="1" GroupName="Numbers" IsChecked="{Binding IsOne, Mode=TwoWay}" />
<RadioButton Grid.Column="2" Grid.Row="1" GroupName="Numbers" IsChecked="{Binding IsOne, Converter={StaticResource NegateBoolConverter}, Mode=TwoWay}" />
<RadioButton Grid.Column="1" Grid.Row="2" GroupName="Letters" IsChecked="{Binding IsA, Mode=TwoWay}" />
<RadioButton Grid.Column="2" Grid.Row="2" GroupName="Letters" IsChecked="{Binding IsA, Converter={StaticResource NegateBoolConverter}, Mode=TwoWay}" />
Negate bool converter simply returns the inverse of the bool value.
--End Edit--
The problem comes when I have a list of two of these objects in my list view. The first object (object 1) for example has 'One' and 'A' selected.
If I then select 'B' in object 2, the selection will try and change for object 1 as well (which messes everything up)
Here is an example of what I am hoping to achieve:
Has anyone seen this behaviour? On winforms it's fixed by setting a new BindingContext for each item but I don't know whether this is the same in WPF.
I am working in a fully MVVM environment, here is the code for the listview:
<ListView Name="ObjectsListView" ItemsSource="{Binding MyObjectList}" SelectedItem="{Binding SelectedObject, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl>
<local:MyObject DataContext="{Binding}"/>
</ContentControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Please can someone let me know how to achieve separate behaviour in my listview items?
Thank you in advance.
Problem is that each item of listview creates radiobutton which the same GroupName, in wpf it is only possible to select one radiobutton from group.
So if you got two items ore more the radiobuttons will belong to one common Group.
Solution:
I add two new properties to Model (your class of SelectObject)
private string _NameFirst;
public string NameFirst
{
get { return _NameFirst; }
set
{
if (value != _NameFirst)
{
_NameFirst = value;
NotifyPropertyChanged();
}
}
}
private string _NameSecond;
public string NameSecond
{
get { return _NameSecond; }
set
{
if (value != _NameSecond)
{
_NameSecond = value;
NotifyPropertyChanged();
}
}
}
Each model item of the list should have different NameFirst and NameSecond, there are multiple ways to do so. E.g item1 : (NameFirst="A1",NameSecond="B1"), item2 : (NameFirst="A2", NameSecond="B2".
Also I modify the ContentControl of ListView I modified to StackPanel only for simplicity, you can stay with grid as well.
<StackPanel>
<RadioButton Grid.Column="0" Content="One" Grid.Row="0" GroupName="{Binding NameFirst}" IsChecked="{Binding IsOne, Mode=TwoWay}" />
<RadioButton Content="Two" GroupName="{Binding NameFirst}" IsChecked="{Binding IsOne, Converter={StaticResource NegateBoolConverter}, Mode=TwoWay}" />
<RadioButton Content="A" GroupName="{Binding NameSecond}" IsChecked="{Binding IsA, Mode=TwoWay}" />
<RadioButton Content="B" GroupName="{Binding NameSecond}" IsChecked="{Binding IsA, Converter={StaticResource NegateBoolConverter}, Mode=TwoWay}" />
</StackPanel>
I'm working on my first Xamarin app. I want to make a listview with grouping and it succeeded. The only problem I have that it won't scroll. My other listview on another page scrolls without a problem, but my listview with grouping won't do that. I tried this both on an Android simulator as on my Android phone (I don't have a macbook or something to test on iOS), and it won't scroll on either of them. I looked it up but a lot of people put the listview in a scrollview, and I didn't do that.
This is my XAML code:
<StackLayout Margin="10" Orientation="Vertical">
<Label Text="{Binding Title}" FontAttributes="Bold"
FontSize="20" HorizontalOptions="CenterAndExpand" />
<Label Text="{Binding Subtitle}" FontAttributes="Italic"
FontSize="15" HorizontalOptions="CenterAndExpand" />
<ListView HasUnevenRows="True" SeparatorColor="Accent"
VerticalOptions="FillAndExpand" IsEnabled="False"
IsGroupingEnabled="True" GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="4">
<StackLayout Orientation="Horizontal" Margin="10,7,10,1">
<Label Text="{Binding Location, StringFormat='{0}'}"
FontAttributes="Bold" FontSize="16" />
<Label Text="{Binding DateTime, StringFormat='{0:dd/MM/y HH:mm}'}"
HorizontalOptions="EndAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding ElapsedTimeOnLocation}"
HorizontalOptions="Start"
Margin="10,0,10,7" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I put it in an MVVM structure, and the grouping I did with a MVVM helper that I got from here https://channel9.msdn.com/Shows/XamarinShow/The-Xamarin-Show-12-MVVM-Helpers .
My code behind that is meant for the grouping is the following:
public ObservableRangeCollection<Grouping<string, ScanViewModel>> ScansGroup { get; } =
new ObservableRangeCollection<Grouping<string, ScanViewModel>>();
void Group()
{
var grouped = from scan in Scans
group scan by scan.Day into scanGroup
select new Grouping<string, ScanViewModel>(scanGroup.Key, scanGroup);
ScansGroup.ReplaceRange(grouped);
}
The grouping shows perfectly and the list too. The only problem is that
I can't scroll. Can someone help me?
I've figured it out. There's nothing wrong with your grouping. In your code you set IsEnabled="False" to the ListView. It makes the listview's scroll ability to be handled just if you drag from an enabled item inside the ListView, and even thus, the scroll is like belittled and with a very bad user experience.
Just set your ListView like this:
<ListView HasUnevenRows="True"
SeparatorColor="Accent"
VerticalOptions="FillAndExpand"
IsEnabled="True"
IsGroupingEnabled="True"
GroupDisplayBinding="{Binding Key}"
ItemsSource="{Binding ScansGroup}">
...
</ListView>
I can't tell you if it was designed to be this way, or if it 's a bug, but it is the cause of your issue.
It's probable you have done this to handle some unwanted behavior. If so, let us know what is specifically your intend with this IsEnabled="False", then we'll be able to help you with this.
I hope it helps you.
You didn't add the template for the group. Add this in the <ListView>
<ListView.GroupHeaderTemplate >
<DataTemplate >
<ViewCell Height="28" >
<Label Text="{Binding GroupName}" VerticalTextAlignment="Center" HorizontalOptions="Start" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
Also, in the code bend or ViewModel, you need to bind to a group class of the items you want in the list such as:
public class GroupDetails : ObservableCollection<SomeItemsToShow>
{
public string GroupName { get; set; }
}
Check out this Example for more details:
https://xamarinhelp.com/xamarin-forms-listview-grouping/