Silverlight4 Element Binding Weirdness - data-binding

I'm a bit of a .net newbie and I've been working on my first big silverlight project. So pardon the lack of lingo and the length of the question. But my problem is as follows.
The project is being built according to the MVVM pattern ( in this case I'm using LightMVVM ). Most of the views contain ListBoxes. These listboxes need to handle multiple different types of data each of which has it's own visual look. After some poking around I decoded tp try this implementation for datatemplate selection:
http://silverscratch.blogspot.com/2010/04/changing-data-templates-at-run-time.html
Some of my items, however, have sub controls that need to talk to the viewmodel. From what I've been reading Commands with element bindings is the best ways to handle this.
So, for example:
<Grid x:Name="NavMainLayoutRoot" DataContext="{Binding Source={StaticResource NavMainViewModelDataSource}}" Margin="15,0,0,0">
....
<ListBox x:Name="MenuListBox" HorizontalAlignment="Left" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" ItemsSource="{Binding Items}" ItemContainerStyle="{StaticResource MainNavigationButtonStyle}" Padding="0" VerticalAlignment="Top" >
<ListBox.RenderTransform>
<CompositeTransform/>
</ListBox.RenderTransform>
<ListBox.ItemTemplate>
<DataTemplate>
<xxxControls:SelectableContentControl TemplateName="{Binding Path=Type}" Content="{Binding Details}" IsTabStop="{Binding IsHitTestEnabled}">
<xxxControls:SelectableContentControl.Templates>
<DataTemplate>
<local:GenericItem />
</DataTemplate>
<DataTemplate x:Name="navbutton">
<local:MainNavItem />
</DataTemplate>
</xxxControls:SelectableContentControl.Templates>
</xxxControls:SelectableContentControl>
</DataTemplate>
</ListBox.ItemTemplate>
....
And MainNavItem, simplified is:
<Grid x:Name="NavItemRoot" VerticalAlignment="Top" Margin="0,0,0,0">
<Button Content="{Binding Label}" VerticalAlignment="Top" Style="{StaticResource MainNavItemButtonStyle}" HorizontalAlignment="Left" Margin="5,0" Command="{Binding DataContext.NavButtonClick, ElementName=NavMainLayoutRoot}"/>
</Grid>
The problem is that this didn't work. So for grins I went ahead and copy and pasted the code for the MainNavItem directly into the tag and like magic it started working.
Since I reuse a lot of these item templates all over the application, having them in nice contained external files is very nice and not something I want to give up.
(( Thinking about it, this example is not the best, suffice it to say that some of these data templates contain multiple controls and I can't just use selectedItem on the listbox to handle the selected events. ))
So any suggestions are welcome. What's the best practice here?

My first thought is that something in your MainNavItem user control is setting its DataContext to something else. If you don't set the DataContext it should automatically pick it up from the current item in your MenuListBox.
You can try creating a test value converter and putting a breakpoint in it to check what the data context is at runtime.
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.Convert(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value; // put break point here to test data binding
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.ConvertBack(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value;
}
}
And modify your MainNavItem to look like this in order to break in the TestConverter at runtime.
<UserControl.Resources>
<ResourceDictionary>
<TestConverter x:Key="TestConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="NavItemRoot" DataContext="{Binding Converter={StaticResource TestConverter}}">
<Button Content="{Binding Path=Label, Converter={StaticResource TestConverter}}" />
</Grid>
This will help you determine the issue with data binding.
I suspect that the problem with your command is that you're using element-to-element data binding to attempt to bind to an element that is outside of the user control you're currently within. This won't work. Instead, try setting up your NavMainViewModelDataSource static resource in App.xaml, then you can bind directly to it from your user control.
<Button Content="{Binding Label}" Command="{Binding Path=NavButtonClick, Source={StaticResource NavMainViewModelDataSource}}" />

Related

How to create a If Else condition with xaml binding

I'm trying to create a XAML UI based on the condition.
<StackLayout Orientation="Horizontal">
<!--IF WorkEmailAddress != NULL && WorkEmailAddrress != ""-->
<!-- BEGIN IF -->
<Label Text="{Binding WorkEmailAddress}" Style="{StaticResource labelListItem}"></Label>
<Image HeightRequest="16" HorizontalOptions="End" VerticalOptions="Center" Source="arrow.png" Margin="0,0,15,0"></Image>
<!-- END IF -->
<!-- ELSE -->
<Label Text="Add new email" Style="{StaticResource labelLinkItem}">
</StackLayout>
Could one please let me know how to add a IF ELSE condition with in the XAML to dynamically create a UI based on the value returned from the backend.
You can't do this completely in XAML. Probably the best way to go is to add a bool property to your view model named HasWorkEmailAddress (I'm assuming you have one, and that's where WorkEmailAddress lives) which returns true if there's a non-null, non-empty value for WorkEmailAddress.
You can then bind the first label and Image's IsVisible property to this bool.
You can also create an InverseBooleanConverter, which will implement IValueConverter. The Convert method will simply take a bool and negate it, and return that value. Bind your second labels' IsVisible to the same bool, but specify the InverseBooleanConverter as the binding's Converter. It will then show only if the HasWorkEmailAddress returns false. The labels binding will look like this:
<Label IsVisible="{Binding HasWorkEmailAddress, Converter={StaticResource InverseBooleanConverter}}" />
If you don't want to write your own converter, one exists in the FreshEssentials Nuget package.
One last thing; if its possible for WorkEmailAddress to change while the page is being shown, you'll need to make sure you raise a PropertyChanged event for the HasWorkEmailAddress property, or your view will not change appropriately.

Tab icons in a Xamarin.Forms UWP TabbedPage?

When putting together a TabbedPage in Xamarin.Forms, how do I get UWP to use the page's Icon property?
It looks like UWP could support this just fine, if I configure my Forms attributes/files correctly.
Here's my TabbedPage XAML. The icons are all set up and working for iOS and Android, and even the on-page Image in UWP renders fine (meaning the files are likely in the project correctly).
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:tabbed"
x:Class="tabbed.MainPage">
<TabbedPage.Children>
<local:InitialPage Title="Tab1" Icon="star.png" />
<ContentPage Title="Tab2" Icon="gear.png">
<ContentPage.Content>
<StackLayout>
<Label Text="A nice label." />
<Image Source="star.png" /><!-- works here -->
</StackLayout>
</ContentPage.Content>
</ContentPage>
</TabbedPage.Children>
</TabbedPage>
I outlined how this is possible here http://depblog.weblogs.us/2017/07/12/xamarin-forms-tabbed-page-uwp-with-images/
In short, you need to change the default HeaderTemplate that is being used by UWP. But due to the way Xamarin forms is started, this is not straightforward.
So you need to inject a custom template into the resource dictionary.
Example project is up on Github here https://github.com/Depechie/XamarinFormsTabbedPageUWPWithIcons
Longer detail:
You need to supply your own TabbedPageStyle and switch out the one that Xamarin is using for their UWP rendering.
So the new style contains an Image where we data bind the Source to the Xamarin Icon property.
<Style x:Key="TabbedPageStyle2" TargetType="uwp:FormsPivot">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding Icon, Converter={StaticResource IconConverter}}" Width="15" Height="15" />
<TextBlock Name="TabbedPageHeaderTextBlock" Text="{Binding Title}"
Style="{ThemeResource BodyTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The actual style switching is done in the App.Xaml.cs like this
((Style)this.Resources["TabbedPageStyle"]).Setters[0] = ((Style)this.Resources["TabbedPageStyle2"]).Setters[0];
You'll also need a converter to be sure the Image control understands the Icon source giving by Xamarin
public class IconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null && value is Xamarin.Forms.FileImageSource)
return ((Xamarin.Forms.FileImageSource)value).File;
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
As it is currently, the UWP TabbedPage renderer does not use the Icon property at all, so getting tab icons will require a custom renderer. Even the official UWP samples don't actually seem to have this baked-in, requiring a custom UserControl.
The Android TabbedPageRenderer and iOS TabbedRenderer, and even the macOS TabbedPageRenderer, use the Icon property to adjust the tab UI, but the UWP renderer would need updating to make this work.

Xceed Datagrid won't bind to DataTemplate of Xceed control

I have the following DataTemplates defined. The TextBlock works the xctk:ShortUpDown does not. In fact whenever I use a control from another namespace it doesn't work (i.e. no data displayed or updated
<DataTemplate x:Key="intDataTemplate">
<TextBlock Text="{Binding StringFormat=\{0:F0\}}"/>
</DataTemplate>
<DataTemplate x:Key="hexDataTemplate">
<xctk:ShortUpDown ParsingNumberStyle="HexNumber"/>
</DataTemplate>
These are the column definitions. There is no CellEditorTemplate available.
<xcdg:Column FieldName="Coefficient" Width="75"
CellContentTemplate="{StaticResource hexDataTemplate}" ReadOnly="False"/>
<xcdg:Column FieldName="Measured" Width="75" CellHorizontalContentAlignment="Right"
CellContentTemplate="{StaticResource intDataTemplate}" />
There just doesn't seem to be a lot of example code out there. The columns are auto generated.
Any suggestions are appreciated.
The CellContentTemplate is for display purposes only. If you put a control meant for editing in it, such as a ShortUpDown, you will get weird results.
Editor controls should be defined in the CellEditor. Also, don't forget to set the CellEditorBinding to connect it to the underlying value.
<xcdg:CellEditor x:Key="hexCellEditor">
<xcdg:CellEditor.EditTemplate>
<DataTemplate>
<xctk:ShortUpDown Value="{xcdg:CellEditorBinding}" ParsingNumberStyle="HexNumber"/>
</DataTemplate>
</xcdg:CellEditor.EditTemplate>
</xcdg:CellEditor>
<xcdg:Column FieldName="Measured" CellEditor="{StaticResource hexCellEditor}" ... />

WPF designer gives exception when databinding a label to a checkbox

I'm sure it's something stupid, but I'm playing around with databinding. I have a checkbox and a label on a form. What I'm trying to do is simply bind the Content of the label to the checkbox's IsChecked value.
What I've done runs fine (no compilation errors and acts as expected), but if I touch the label in the XAML, the designer trows an exception:
System.NullReferenceException
Object reference not set to an instance of an object.
at MS.Internal.Designer.PropertyEditing.Editors.MarkupExtensionInlineEditorControl.BuildBindingString(Boolean modeSupported, PropertyEntry propertyEntry)
at
<Window x:Class="UnitTestHelper.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:FileSysCtls="clr-namespace:WPFFileSystemUserControls;assembly=WPFFileSystemUserControls"
xmlns:HelperClasses="clr-namespace:UnitTestHelper"
Title="MainWindow" Height="406" Width="531">
<Window.Resources>
<HelperClasses:ThreestateToBinary x:Key="CheckConverter" />
</Window.Resources>
<Grid Height="367" Width="509">
<CheckBox Content="Step into subfolders" Height="16" HorizontalAlignment="Left" Margin="17,254,0,0" Name="chkSubfolders" VerticalAlignment="Top" Width="130" IsThreeState="False" />
<Label Height="28" HorizontalAlignment="Left" Margin="376,254,0,0" Name="lblStepResult" VerticalAlignment="Top" Width="120" IsEnabled="True" Content="{Binding IsChecked, ElementName=chkSubfolders, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource CheckConverter}}" />
</Grid>
The ThreeStateToBinary class is as follows:
class ThreestateToBinary : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
return "Checked";
else
return "Not checked";
//throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value == "Checked");
//throw new NotImplementedException();
}
#endregion
}
Quite honestly, I'm playing around with it at this point. It was originally simpler (not using the ValueConverter) but was displaying similar behavior when I simply had the content set to:
Content="{Binding IsChecked, ElementName=chkSubfolders, UpdateSourceTrigger=PropertyChanged}"
Any ideas?
Thanks,
John
Try removing UpdateSourceTrigger=PropertyChanged. In this case, the checkbox is your source and the label is your target. The label doesn't change, and furthermore, you set the mode to OneWay which binds only from the source to the target. Therefore, it's meaningless to tell it the binding to update the source on property changed. It might not be causing your problem, but it seems suspect (or at least weird).

Updating SilverLight lists when Bound in-memory collection gets a new member

I'm trying to pupulate one of three listboxes from a (fourth) source list box. The source has a list of school Subjects which are classified as elementary, middle or high school subjects. The source listbox is a list of checkboxes. The user clicks on the checkbox and one of the other three are intended to get a copy of the Subject object from the source list. I've got the thing wired up and successfully hit a CheckBox_Changed method. I can successfully locate the Subject instance from the source list and add it to the target list's Source array.
What I can't do is show the update on the Silverlight control that the target array is bound to.
Any ideas?
Thanks
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
var cb = (CheckBox)sender;
var children = ((Grid)cb.Parent).Children;
// cb has a sibling TextBlock item that has the index of the item in
// the list of subjects
var ch2 = children[1] as TextBlock;
var subjectIndexStr = ch2.Text;
var myWorkingSubject = workingSubjectList[int.Parse(subjectIndexStr)];
switch (myWorkingSubject.SubjectLevelId)
{
// updates to the elementarySubjects, middleSubjects and highSubjects
// don't get reflected in the lists that use them as a resource.
case (int)SubjectLevels.Elementary:
elementarySubjects.Add(myWorkingSubject);
break;
case (int)SubjectLevels.Middle:
middleSubjects.Add(myWorkingSubject);
break;
case (int)SubjectLevels.High:
highSubjects.Add(myWorkingSubject);
break;
default: break;
}
}
// this is how the target classes are declared.
public class SubjectsElementary : ObservableCollection<WorkingSubject>
{
}
public class SubjectsMiddle : ObservableCollection<WorkingSubject>
{
}
public class SubjectsHigh : ObservableCollection<WorkingSubject>
{
}
Here are snippets from the .xaml file
<TutorRouterSvc:WorkingSubjectList x:Key="subjects" />
<TutorRouterSvc:SubjectsElementary x:Key="elementarySubjects" />
<TutorRouterSvc:SubjectsMiddle x:Key="middleSubjects" />
<TutorRouterSvc:SubjectsHigh x:Key="highSubjects" />
<ListBox x:Name="subjectList" ItemsSource="{Binding Mode=OneWay, Source={StaticResource subjects}}">
<ListBox.Resources>
</ListBox.Resources>
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox Margin="0,0,8,0" x:Name="elementarySubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource elementarySubjects}}"
Background="#FFE75151" Grid.Row="0">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Margin="0,0,8,0" x:Name="middleSubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource middleSubjects}}"
Background="#FFE75151" Grid.Row="1">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Margin="0,0,8,0" x:Name="highSubjectList"
ItemsSource="{Binding Mode=OneWay, Source={StaticResource highSubjects}}"
Background="#FFE75151" Grid.Row="1">
<ListBox.ItemTemplate>
<StaticResource ResourceKey="DataSubjectsTemplate1"/>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I'm not quite sure, but this may be fixable by doing the changes inside a Dispatch.BeginInvoke().
You could refactor the switch statement to a new method called UpdateListBox, then call it:
Dispatcher.BeginInvoke(() => UpdateListBox(myWorkingSubject.SubjectLevelId))
Maybe this is happening because the XAML is Newing up a new instance of your objects, which it's databinding to.
Try adding this to the cosntructor on your Page.xaml.cs (or where ever the control is located);
_subjects = Resources["subjects"] as WorkingSubjectsList;
_elementarySubjects = Resources["elementarySubjects"] as SubjectsElementary;
etc...
Maybe that will help. I've implemented the same concept by binding listboxes to Observable collections on several occassions and haven't experienced what you're encountering.
I do have a couple of suggestions:
have you tried this on your check changed event?
workingsubject _item = workingSubjectList[subjectsList.selectedindex];
switch (_item.SubjectLevel) //I'm assuming this property as you have the ID and it looks to be an enumeration
{
case Elementary:
elementarySubjects.Add(_item):
break;
case Middle:
middleSubjects.Add(_item):
break;
case High:
highSubjects.Add(_item):
break;
case default:
throw new Exception("Unrecognized Subject Level");
}
hth.

Resources