Binding data to Combobox in custom activity designer - workflow

I have a custom activity, with a single in argument which is a string. However, rather than allow the designer to enter an arbitrary string, I want the designer to be presented with a Combobox with the list of options, the options are dynamic and are loaded from a database into a List<> collection.
My problem is I have no clue how to bind the Combobox in the designer to this list and have the selection set to the in argument of the activity. Visually I have the activity designer working, it is just this one step.

Normally, I would write the activity with a property rather than an InArgument. This simplifies the scenario:
<ComboBox ItemsSource="{Binding Path=ValidOptions}"
SelectedValue="{Binding Path=ModelItem.MyStringProperty, Mode=TwoWay}"/>
(here ValidOptions is some Collection property on your ActivityDesigner class. MyStringProperty is some public get/set/ property on the underlying activity such as:
public string MyStringProperty { get; set; }
)
The problem you will have if you add InArgument to the mix is that the string values from the combo box cannot be directly assigned to a ModelItem expecting InArgument<string>. This is fixable using a custom IValueConverter in your binding.

The previous answers are helpful but were not enough for me. Eventually I found a terrific article from 2012, in Microsoft's .Net 4.5 Developer Guide: Binding a custom activity property to a designer control. That article was almost the full answer - except for a minor bug in the custom converter class, and a major flaw: that technique will save a value from the ComboBox, but it will not restore it when you re-open your workflow.
Microsoft's Ron Jacobs has another answer for custom activity designers. I ended up combining the two to get a working solution.
Custom Designer
The ModelToObjectValueConverter was an incredibly helpful resource, allowing me to skip creating my own IValueConverter. In the ObjectDataProvider you see me loading a list of strings by calling a static method, People.GetPeople(). The ComboBox binds to that provider as the item source, but binds the selected value to the Person property on the custom Activity (below)
<sap:ActivityDesigner x:Class="ActivityLibrary1.ComboBoxActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
xmlns:c="clr-namespace:ActivityLibrary1">
<sap:ActivityDesigner.Resources>
<ResourceDictionary>
<sapc:ModelToObjectValueConverter x:Key="ModelToObjectValueConverter" />
<ObjectDataProvider x:Key="people" ObjectType="{x:Type c:People}" MethodName="GetPeople"/>
</ResourceDictionary>
</sap:ActivityDesigner.Resources>
<Grid>
<Label Content="Person" HorizontalAlignment="Left" VerticalAlignment="Top" />
<ComboBox HorizontalAlignment="Left"
Margin="66,0,0,0"
VerticalAlignment="Top"
Width="120"
SelectedValue="{Binding Path=ModelItem.Person, Mode=TwoWay, Converter={StaticResource ModelToObjectValueConverter} }"
ItemsSource="{Binding Source={StaticResource people}}">
</ComboBox>
</Grid>
</sap:ActivityDesigner>
Custom Code Activity
Note that this uses a property rather than an InArgument, which makes binding the ComboBox easier.
[Designer(typeof(ComboBoxActivityDesigner))]
public class CodeActivity1 : CodeActivity
{
public string Person { get; set; }
protected override void Execute(CodeActivityContext context)
{
// Just to demonstrate that it worked
MessageBox.Show(Person);
}
}
Workflow
Now the custom activity, CodeActivity1, can be dragged onto a workflow. When you make a selection, the selected value will appear in the properties pane. Save the workflow. Close and re-open. The previously-selected value will persist as desired.

One way to solve it is to define your own ComboBoxEditor which derives from UITypeEditor.
Expose the collection you want to bind this combobox in the activity class and decorate your bindable property in Activity class with following attribute :
[EditorAttribute(typeof(CustomListBoxEditor), typeof(System.Drawing.Design.UITypeEditor))]
Also in the custom comboboxEditor you will have to modify your
EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) method to get the collection and bind it to the combobox.

Related

DataTriggerBehavior Binding to Property on ViewModel

I'm trying to bind a DataTriggerBehavior to a Property on my ViewModel, but it doesn't ever fire.
I've used DataTriggerBehaviors bound to various Properties of Controls with no trouble but can't get the VM binding to work.
DataContext is set to the VM.
I can see the binding value in debug but nothing triggers.
I've tested the InvokeCommandAction by changing the DataTriggerBehavior to an EventTriggerBehavior so that works fine.
<AppBarButton Icon="Library">
<i:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding HelpPhase}" ComparisonCondition="Equal" Value="Add" >
<core:InvokeCommandAction Command="{Binding DataContext.StoreRateCommand, ElementName=LayoutRoot}"/>
</core:DataTriggerBehavior>
</i:Interaction.Behaviors>
</AppBarButton>
In VM (inherits VMBase that implements IPCN)
Private mHelpPhase As String
Public Property HelpPhase() As String
Get
Return Settings.HelpPhase
End Get
Set(value As String)
SetProperty(Settings.HelpPhase, value)
End Set
End Property
The EventTriggerBehavior listens for a specific event on its source and executes an action when the event is fired. It is different from the DataTriggerBehavior.
The DataTriggerBehavior performs an action when the data the behaviors is bound to meets a specified condition. In your question, when the bound data of the HelpPhase's value change to "Add", the behavior triggers an action to fire the command.
You should be able check if you have bind the HelpPhase to the DataTriggerBehavior and set the "Add" to the HelpPhase. You can bind the HelpPhase to Text property of TextBlock if the TextBlock show "Add".
There is an official DataTriggerBehavior sample, please refer the XamlBehaviors sample.

Proper way to do dialogs using Prism and UWP

I'm not sure if I'm thinking about this in the correct way. I have a list of objects, and I would like the user to be able to edit and view the properties of a specified object. My initial thought is great, I'll pop up a dialog that has textboxes and let the user edit to their heart's content until they press either Ok or Cancel.
I'm on UWP, and using Prism for all of my MVVM needs. It's taken me a while, but I understand creating Views and their associated ViewModels, commands, etc. So far, I think I've done a good job keeping the view logic and business logic separated.
I've searched, but haven't found how to show a dialog in a way that follows MVVM principles. The two things that seem to be coming up the most are to use Interaction requests (which don't appear to exist using Prism on UWP), and creating a custom Content Dialog and showing it by calling ShowAsync in a method in the parent view's associated ViewModel (which seems to be counter to MVVM principles).
So, how do I either show a dialog that is defined using XAML and has an associated ViewModel (preferable since it is similar to what I'm familiar with), or another way I can tackle this problem?
Using MVVM the proper place for opening a dialog is in the ViewModel.
Usually I do something like this in your scenario:
Create an interface for showing dialogs:
public interface IWindowService
{
void OpenViewModelInWindow(ViewModelBase vm, string title, bool resizeable = true);
void CloseViewModelInWindow(ViewModelBase vm);
}
Implement this interface in UI layer:
public class WindowService : IWindowService
{
private List<Window> _windows = new List<Window>();
public void OpenViewModelInWindow(ViewModelBase vm, string title, bool resizeable = true)
{
var window = new Window
{
Title = title,
Content = vm,
Owner = Application.Current.MainWindow,
WindowStartupLocation = WindowStartupLocation.CenterOwner,
ShowInTaskbar = false,
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = resizeable ? ResizeMode.CanResize : ResizeMode.NoResize
};
_windows.Add(window);
window.ShowDialog();
}
public void CloseViewModelInWindow(ViewModelBase vm)
{
_windows.Single(w => w.Content == vm).Close();
}
}
In your App.xaml you need to define DataTemplates so that when you set the Content property of the window the corresponding View created in the window.
<DataTemplate DataType="{x:Type viewModel:AViewModel}">
<views:AUserControl />
</DataTemplate>
Then you can use the IWindowService from the ViewModel, you should inject it by constructor injection.
This way you are not referencing framework specific classes directly from the ViewModel. ViewModel has a reference only to an IWindowService. This has a benefit also when you want ot write unit test for a viewmodel. You can mock this service so that when the unit test is running it should not open a dialog.

Listen to ItemTapped Event in FreshMvvm

In my project I have a list view, now listening to SelectedItem change is easy, every tutorial has that, but I can't find anything on using ItemTapped event.
What do I bind the event to in the modelPage?
Thanks,
Mike
Since ItemTapped is an event and not a Command (or BindableProperty at all) you cannot use it directly from you PageModel.
They have invented something like Behaviors for this. With Behaviors you can turn an Event to a Command.
While there are third party plugins which do this like Corcav's one, it is also built into Xamarin.Forms now.
Let me explain it by the Corcav one, other implementations should be similar. Also I'm assuming you're using XAML.
First of all, install the NuGet and don't forget to include the right namespace into your page, which means adding something like: xmlns:behaviors="clr-namespace:Corcav.Behaviors;assembly=Corcav.Behaviors"
Now under your ListView declare your Behaviors like so:
<!-- ... more XAML here ... -->
<ListView IsPullToRefreshEnabled="true" RefreshCommand="{Binding RefreshDataCommand}" IsRefreshing="{Binding IsBusy}" IsVisible="{Binding HasItems}" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" CachingStrategy="RecycleElement">
<behaviors:Interaction.Behaviors>
<behaviors:BehaviorCollection>
<behaviors:EventToCommand EventName="ItemSelected" Command="{Binding ItemSelectedCommand}" />
</behaviors:BehaviorCollection>
</behaviors:Interaction.Behaviors>
<!-- ... more XAML here ... -->
Note that is is a collection, so you could add more if you want (also in other scenarios).
Also note that I did in fact user the SelectedItem as well. This is probably what you want because else the item you tapped will stay selected. So the SelectedItem property doesn't do much more than set it back to null (hence the TwoWay). But you can also take the actual selected item from there.
So now in your PageModel declare a Command and assign it with something like this:
private void ItemSelected()
{
// Open the article page.
if (_selectedItem != null)
{
CoreMethods.PushPageModel<GroupArticlePageModel>(_selectedItem, false, true);
}
}
_selectedItem being the property to which the tapped item is assigned.
Of course you could do it even better and supply the behavior with a CommandParameter in which you put the tapped item reference.

Format date shown on DatePicker

I created a Pivot Project on Visual Studio 2013.
I created the following control:
<DatePicker Header="Data" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" Width="341"/>
When I tap on it on my phone, it displays the date as M/D/Y. Is it possible to display it as D/M/Y?
Also, don't know if I should create another question, but how can I translate to pt-BR the day/month names shown within the control? As well as that "Choose a date".
The DatePicker format is using user's preferred language and region. This control will automatically look different, for my region it's day/month/year like you wish it to be. I've tried to force the control to use other language, but it seems to be taking it directly from phone settings. Some information you may find here at MSDN:
If you need to allow users to choose a date or select a time, use the standard date and time picker controls. These will automatically use the date and time formats for the user's preferred language and region.
The other bad news you can find here at MSDN about formating that particular control:
Note This section applies to Windows Store apps using C++, C#, or Visual Basic. Formatting is ignored in Windows Phone Store apps.
So in current API it may be hard to change the formatting.
The good news is for the CHOOSE DATE text at the top - it's automatically localized, depending on user's language and region. So you don't have to worry about it if your app supports user's language. But I've not found way to change it to other text.
As for the text displayed inside the button before it's clicked, you can always use Button with DatePickerFlyout, a simple example with apropriate converter:
<Button Content="{Binding ElementName=chosenDate, Path=Date, Converter={StaticResource DateFormatConverter}}">
<Button.Flyout>
<DatePickerFlyout x:Name="chosenDate" />
</Button.Flyout>
</Button>
and the converter class:
public class DateFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
DateTimeOffset chosen = (DateTimeOffset)value;
return string.Format("{0}/{1}/{2}", chosen.Day, chosen.Month, chosen.Year);
// or use chosen.ToString() with format provider
}
public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); }
}

How to retrieve text from RichTextBlock in WinRT

I am using the standard RichTextColumns.cs helper class that is added by VS2012's default Metro Style project template. It uses the RichTextBlock internally to add RichTextColumns. DataBinding is working fine with the following markup
<common:RichTextColumns>
<common:RichTextColumns.ColumnTemplate>
<DataTemplate>
<RichTextBlockOverflow Width="400" Margin="50,0,0,0"/>
</DataTemplate>
</common:RichTextColumns.ColumnTemplate>
<RichTextBlock Width="400">
<Paragraph>
<Run Text="{Binding Content}"/>
</Paragraph>
</RichTextBlock>
Now I have the embedded hyperlinks in 'Content' that are not treated as hyperlinks in WinRT. I need them to behave like hyperlinks. So I want to retrieve the text that's getting bound to the RichTextBlock, tokenize it, insert InlineUI elements that have the HyperlinkButton at appropriate places. Now I could do all this if I could only retrieve the text that is getting bound to the RichTextBlock. Unfortunately I can't seem to 'retrieve' it.
I tried
RichTextBlock value = (RichTextBlock)GetValue(RichTextContentProperty);
valueRun = (Run)((Paragraph)value.Blocks[0]).Inlines[0];
value.Select(((Paragraph)value.Blocks[0]).ContentStart, ((Paragraph)value.Blocks[0]).ContentEnd);
Paragraph para = TokenizeTweet(value.SelectedText);
But the SelectedText is always empty.
However if I do a
value.Blocks.Clear()
it clears out the text that is getting bound. What am I missing?
Simply put, how to retrieve unformatted text from a RichTextBlock in WinRT (Not WPF or Silverlight).
Thanks and Regards,
Sumit.
Instead of trying to retrieve it, why not simply use a converter on the binding?