Segmented textbox in WPF - wpf

Is anyone aware of a free or commercial WPF control that would do something like this:
X character per box, and auto-tabbing to the next box as you complete each box? Similar to the way that license keys are entered for Microsoft products.
I don't think it would be particularly hard to do from scratch, but I'd like to avoid reinventing the wheel if a good example of this already exists.

WPF provides all you need except auto-tabbing to the next control. A behavior can provide that functionality and the result looks like this:
<Grid>
<Grid.Resources>
<local:KeyTextCollection x:Key="keys"/>
</Grid.Resources>
<StackPanel>
<ItemsControl ItemsSource="{StaticResource keys}" Focusable="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Background="AliceBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" Margin="5" MaxLength="4" Width="40">
<i:Interaction.Behaviors>
<local:TextBoxBehavior AutoTab="True" SelectOnFocus="True"/>
</i:Interaction.Behaviors>
</TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Here are the KeyText and KeyTextCollection classes (adjust to taste):
class KeyText
{
public string Text { get; set; }
}
class KeyTextCollection : List<KeyText>
{
public KeyTextCollection()
{
for (int i = 0; i < 4; i++) Add(new KeyText { Text = "" });
}
}
And here is the behavior that implements auto-tab and select-on-focus:
public class TextBoxBehavior : Behavior<TextBox>
{
public bool SelectOnFocus
{
get { return (bool)GetValue(SelectOnFocusProperty); }
set { SetValue(SelectOnFocusProperty, value); }
}
public static readonly DependencyProperty SelectOnFocusProperty =
DependencyProperty.Register("SelectOnFocus", typeof(bool), typeof(TextBoxBehavior), new UIPropertyMetadata(false));
public bool AutoTab
{
get { return (bool)GetValue(AutoTabProperty); }
set { SetValue(AutoTabProperty, value); }
}
public static readonly DependencyProperty AutoTabProperty =
DependencyProperty.Register("AutoTab", typeof(bool), typeof(TextBoxBase), new UIPropertyMetadata(false));
protected override void OnAttached()
{
AssociatedObject.PreviewGotKeyboardFocus += (s, e) =>
{
if (SelectOnFocus)
{
Action action = () => AssociatedObject.SelectAll();
AssociatedObject.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
}
};
AssociatedObject.TextChanged += (s, e) =>
{
if (AutoTab)
{
if (AssociatedObject.Text.Length == AssociatedObject.MaxLength &&
AssociatedObject.SelectionStart == AssociatedObject.MaxLength)
{
AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
};
}
}
If you are not familiar with behaviors, Install the Expression Blend 4 SDK and add this namespaces:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
and add System.Windows.Interactivity to your project.

Related

Can anyone provide a concrete example of WPF "visual inheritance" for a dialog box?

I am an experienced WinForms developer, relatively new to WPF. I have a large WinForms application that uses a couple different base classes to represent dialog boxes. One such example is AbstractOkCancelDialog. That class contains a panel at the bottom of a dialog, with an Ok and Cancel button on the right side of the panel. I'm trying to determine the best way to handle this, as I realize that WPF doesn't provide visual inheritance.
I don't want to have to create OK and Cancel buttons, and place them, for every dialog in the application.
I have read that the way to do this in WPF is with user controls. I can envision creating a user control with OK and Cancel buttons on it. But I don't want to have to manually place that user control on hundreds of dialogs in my application. I'd really like to have something like this:
public AbstractOkCancelDialog = class(Window)
{
protected AbstractOkCancelDialogViewModel _ViewModel;
// AbstractOkCancelDialogViewModel would have commands for OK and Cancel.
// Every dialog would inherit from AbstractOkCancelDialog, and would use
// a viewmodel that inherits from AbstractOkCancelDialogViewModel. In
// this way, all view models would automatically be connected to the OK
// and Cancel commands.
}
I've seen some discussion online about how to create the base class. Those discussions explain how there can't be a xaml file associated with the dialog base class, and I understand that restriction. I just can't figure out how to automatically place the user control with the OK and Cancel buttons.
I'm hoping that someone can point me to a sample solution that shows this kind of structure. Thank you in advance!
Write one dialog class. It's a subclass of Window. It has XAML:
<Window
...blah blah blah...
Title="{Binding Title}"
>
<StackPanel MinWidth="300">
<!-- This is how you place content within content in WPF -->
<ContentControl
Content="{Binding}"
Margin="2"
/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,20,2,2">
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="OK"
Click="OK_Click"
IsDefault="True"
/>
<Button
Margin="2"
MinWidth="60"
DockPanel.Dock="Right"
Content="Cancel"
IsCancel="True"
Click="Cancel_Click"
/>
</StackPanel>
</StackPanel>
</Window>
You can fancy that up endlessly, but this is a decent minimum to give you arbitrary content above a row of right-aligned buttons. Adding more buttons as needed could involve either templating that portion of the window as well, or creating them with an ItemsControl (I've done that in our production code), or a few other options.
Usage:
var vm = new SomeDialogViewModel();
var dlg = new MyDialog { DataContext = vm };
For each dialog viewmodel, consumers must define an implicit datatemplate which provides UI for that viewmodel.
I would suggest writing a dialog viewmodel interface which the consumer is expected to implement.
public interface IDialogViewModel
{
String Title { get; set; }
void OnOK();
// Let them "cancel the cancel" if they like.
bool OnCancel();
}
The window can check if its DataContext implements that interface, and act accordingly. If you like, it could require that interface and throw an exception of it isn't implemented, or it could just talk to it only if it's there. If they don't implement it but they still have a Title property, the binding to Title will still work. Bindings are "duck typed".
Naturally, you can write an OKCancelDialogViewModel or a SelectStringFromListViewModel, write corresponding DataTemplates that implement their UIs, and write nice clean static methods which show them:
public static class Dialogs
{
public static TOption Select<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : class
{
// Viewmodel isn't generic because that breaks implicit datatemplating.
// That's OK because XAML uses duck typing anyhow.
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
Options = options
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return vm.SelectedOption as TOption;
}
return null;
}
// We have to call the value-type overload by a different name because overloads can't be
// distinguished when the only distinction is a type constraint.
public static TOption? SelectValue<TOption>(IEnumerable<TOption> options, string prompt,
string title = "Select Option") where TOption : struct
{
var vm = new SelectOptionDialogViewModel
{
Title = title,
Prompt = prompt,
// Need to box these explicitly
Options = options.Select(opt => (object)opt)
};
if ((bool)new Dialog { DataContext = vm }.ShowDialog())
{
return (TOption)vm.SelectedOption;
}
return null;
}
}
Here's a viewmodel datatemplate for the above selection dialog:
<Application.Resources>
<DataTemplate DataType="{x:Type local:SelectOptionDialogViewModel}">
<StackPanel>
<TextBlock
TextWrapping="WrapWithOverflow"
Text="{Binding Prompt}"
/>
<ListBox
ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption}"
MouseDoubleClick="ListBox_MouseDoubleClick"
/>
</StackPanel>
</DataTemplate>
</Application.Resources>
App.xaml.cs
private void ListBox_MouseDoubleClick(object sender,
System.Windows.Input.MouseButtonEventArgs e)
{
((sender as FrameworkElement).DataContext as IDialogViewModel).DialogResult = true;
}
var a = Dialogs.Select(new String[] { "Bob", "Fred", "Ginger", "Mary Anne" },
"Select a dance partner:");
var b = Dialogs.SelectValue(Enum.GetValues(typeof(Options)).Cast<Options>(),
"Select an enum value:");
Here an example of how to use a custom AlertDialog
UserControl
<UserControl x:Class="Library.Views.AlertMessageDialogView"
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"
xmlns:p="clr-namespace:Library.Properties"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
FlowDirection = "{Binding WindowFlowDirection, Mode=TwoWay}">
<Grid Background="{DynamicResource WindowBackgroundBrush}">
<Canvas HorizontalAlignment="Left" Height="145" VerticalAlignment="Top" Width="385">
<Label HorizontalAlignment="Left" Height="57" VerticalAlignment="Top" Width="365" Canvas.Left="10" Canvas.Top="10" FontSize="14" >
<TextBlock x:Name="txtVocabAnglais" TextWrapping="Wrap" Text="{Binding Message, Mode=TwoWay}" Width="365" Height="57" />
</Label>
<Button x:Name="cmdCancel" Content="{x:Static p:Resources.AlertMessageDialogViewcmdCancel}" Height="30" Canvas.Left="163" Canvas.Top="72" Width="71" Command = "{Binding CancelCommand}" CommandParameter = "null" IsDefault="True"/>
</Canvas>
</Grid>
</UserControl>
ViewModel Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
namespace Library.ViewModel
{
public class AlertMessageDialogViewModel : BindableBaseViewModel
{
public event EventHandler CloseWindowEvent;
private string _title;
private string _message;
public BaseCommand<string> YesCommand { get; private set; }
public BaseCommand<string> CancelCommand { get; private set; }
private WinformsNameSpace.FlowDirection _windowFlowDirection;
public AlertMessageDialogViewModel()
{
CancelCommand = new BaseCommand<string>(cmdCancelBtnClick);
WindowFlowDirection = CustomFuncVar.WindowFlowDirection;
}
public WinformsNameSpace.FlowDirection WindowFlowDirection
{
get
{
return _windowFlowDirection;
}
set
{
_windowFlowDirection = value;
OnPropertyChanged("WindowFlowDirection");
}
}
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public string Title
{
get
{
return _title;
}
set
{
_title = value;
}
}
private void cmdCancelBtnClick(string paramerter)
{
if (CloseWindowEvent != null)
CloseWindowEvent(this, null);
}
}
}
DialogMessage Class
using System;
using System.Windows;
using System.Collections.Generic;
namespace Library.Helpers
{
public static class DialogMessage
{
public static void AlertMessage(string message, string title, Window OwnerWindowView)
{
try
{
//Affichage de méssage de succès enregistrement
AlertMessageDialogViewModel alertDialogVM = new AlertMessageDialogViewModel();
alertDialogVM.Message = message;
alertDialogVM.Title = title;
// Auto Generation Window
FrameworkElement view = LpgetCustomUI.AutoGetViewFromName("AlertMessageDialogView");
view.DataContext = alertDialogVM;
Dictionary<string, object> localVarWindowProperty = new Dictionary<string, object>();
localVarWindowProperty = LpgetCustomUI.GetWindowPropretyType_400x145(Properties.Resources.ApplicationTitle);
CummonUIWindowContainer alertDialogView = new CummonUIWindowContainer(view, null, false, localVarWindowProperty);
//End Auto Generation Window
// Attachement de l'évènement de fermture de View au modèle
alertDialogVM.CloseWindowEvent += new EventHandler(alertDialogView.fnCloseWindowEvent);
if (OwnerWindowView!=null)
{
alertDialogView.Owner = OwnerWindowView;
}
else
{
alertDialogView.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
alertDialogView.ShowDialog();
}
catch (Exception ex)
{
}
}
}
}
CummonUIWindowContainer Class
namespace CummonUILibrary.CummonUIHelpers
{
public class CummonUIWindowContainer : Window
{
public event RoutedEventHandler CmbRootEvtLanguageChange;
private FrameworkElement currentView;
private ContentControl _contentcontainer;
public CummonUIWindowContainer(string usercontrolName)
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer()
{
Contentcontainer = new ContentControl();
currentView = new FrameworkElement();
}
public CummonUIWindowContainer(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
Contentcontainer = new ContentControl();
Contentcontainer.Name = "ContentControl";
SetWindowProperty(view, model, setDataContextToView, WindowPropertyList);
}
public void SetWindowProperty(FrameworkElement view, object model, bool setDataContextToView, Dictionary<string, object> WindowPropertyList)
{
try
{
LinearGradientBrush brush = new LinearGradientBrush();
GradientStop gradientStop1 = new GradientStop();
gradientStop1.Offset = 0;
gradientStop1.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop1);
GradientStop gradientStop2 = new GradientStop();
gradientStop2.Offset = 0.5;
gradientStop2.Color = Colors.Indigo;
brush.GradientStops.Add(gradientStop2);
GradientStop gradientStop3 = new GradientStop();
gradientStop3.Offset = 1;
gradientStop3.Color = Colors.Yellow;
brush.GradientStops.Add(gradientStop3);
this.Background = brush;
CurrentView = view;
Type elementType = this.GetType();
ICollection<string> WindowPropertyListNames = WindowPropertyList.Keys;
foreach (string propertyName in WindowPropertyListNames)
{
PropertyInfo property = elementType.GetProperty(propertyName);
property.SetValue(this, WindowPropertyList[propertyName]);
}
if (setDataContextToView == true & model != null)
{
CurrentView.DataContext = model;
}
if (CurrentView != null)
{
Contentcontainer.Content = CurrentView;
}
//Contentcontainer.Margin = new Thickness(0,0, 0, 0);
IAddChild container=this;
container.AddChild(Contentcontainer);
}
catch (Exception ex)
{
}
}
public void fnCloseWindowEvent(object sender, EventArgs e)
{
this.Close();
}
public ContentControl Contentcontainer
{
get
{
return _contentcontainer;
}
set
{
_contentcontainer = value;
}
}
public FrameworkElement CurrentView
{
get
{
return currentView;
}
set
{
if (this.currentView != value)
{
currentView = value;
//RaisePropertyChanged("CurrentView");
}
}
}
private void cmbLanguage_SelectionChanged(object sender, RoutedEventArgs e)
{
//CmbRootEvtLanguageChange(sender, e);
}
}
}
How to use the Class
DialogMessage.AlertMessage("My Custom Message", "My Custom Title Message");
Thats how i would do it
Create an abstract base class for your dialog and changing the corresponding ControlTemplate
AbstractOkCancelDialog
public abstract class AbstractOkCancelDialog : Window
{
public static readonly DependencyProperty CancelCommandParameterProperty =
DependencyProperty.Register(
"CancelCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty CancelCommandProperty =
DependencyProperty.Register(
"CancelCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
public static readonly DependencyProperty OkCommandParameterProperty =
DependencyProperty.Register(
"OkCommandParameter",
typeof(object),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((object) null));
public static readonly DependencyProperty OkCommandProperty =
DependencyProperty.Register(
"OkCommand",
typeof(ICommand),
typeof(AbstractOkCancelDialog),
new FrameworkPropertyMetadata((ICommand) null));
static AbstractOkCancelDialog()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AbstractOkCancelDialog), new
FrameworkPropertyMetadata(typeof(AbstractOkCancelDialog)));
}
public ICommand CancelCommand
{
get => (ICommand) GetValue(CancelCommandProperty);
set => SetValue(CancelCommandProperty, value);
}
public object CancelCommandParameter
{
get => GetValue(CancelCommandParameterProperty);
set => SetValue(CancelCommandParameterProperty, value);
}
public ICommand OkCommand
{
get => (ICommand) GetValue(OkCommandProperty);
set => SetValue(OkCommandProperty, value);
}
public object OkCommandParameter
{
get => GetValue(OkCommandParameterProperty);
set => SetValue(OkCommandParameterProperty, value);
}
}
Style
Put in Generic.xaml[?]
<Style
BasedOn="{StaticResource {x:Type Window}}"
TargetType="{x:Type local:AbstractOkCancelDialog}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AbstractOkCancelDialog}">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentPresenter />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="1"
Margin="5"
Command="{TemplateBinding OkCommand}"
CommandParameter="{TemplateBinding OkCommandParameter}"
Content="Ok"
DockPanel.Dock="Right" />
<Button
Grid.Column="2"
Margin="5"
Command="{TemplateBinding CancelCommand}"
CommandParameter="{TemplateBinding CancelCommandParameter}"
Content="Cancel"
DockPanel.Dock="Right" />
</Grid>
</Grid>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now you can create your individual dialogs like you would create any other window
Brief example:
TestDialog.xaml
<local:AbstractOkCancelDialog
x:Class="WpfApp.TestDialog"
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:local="clr-namespace:WpfApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestDialog"
Width="800"
Height="450"
OkCommand="{x:Static local:Commands.OkWindowCommand}"
OkCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
CancelCommand="{x:Static local:Commands.CancelWindowCommand}"
CancelCommandParameter="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<Grid>
<!-- Content -->
</Grid>
</local:AbstractOkCancelDialog>
TestDialog.xaml.cs
public partial class TestDialog : AbstractOkCancelDialog
{
...
}

WPF Disptacher processing error

I have the following xaml in my WPF project
<ItemsControl ItemsSource="{Binding ParentJob.JobSamples}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander ExpandDirection="Down" IsExpanded="{Binding Path=IsExpanded}">
<Expander.Header>
<BulletDecorator>
<Label Content="{Binding Identifier}"></Label>
</BulletDecorator>
</Expander.Header>
<ItemsControl ItemsSource="{Binding Path=SelectedAnalyticalTests}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="4" Content="{Binding AnalyticalTestName}" Foreground="{DynamicResource InputFontColor}" Checked="{Binding IsSelected}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My objects are set up such that ParentJob has an ObservableCollection of JobSample objects
and JobSample has an ObservableCollection of AnalyticalTest objects called SelectedAnalyticalTests
If I load up a ParentJob that has JobSamples that don't contain any SelectedAnalyticalTests I get each of the JobSamples listed correctly.
If I attempt to load a ParentJob that has JobSamples that do have SelectedAnalyticalTests I get the following error:-
'Disptacher processing has been suspended, but messages are still being processed'
Has this got to do with my IsExpanded attribute? How can I get around this?
The ViewModel is broken up into 3 partial classes as follows:-
public partial class JobRepeatViewModel : ViewModelBase
{
public JobRepeatViewModel(MainWindowViewModel mainWindowVM)
{
this.MainWindowViewModel = mainWindowVM;
try
{
DependencyObject dep = new DependencyObject();
if (DesignerProperties.GetIsInDesignMode(dep))
{
return;
}
this.DA = new ACSDataAccess();
LoadDropdowns();
}
catch (Exception ex)
{
MessageBox.Show(ex.InnerException.ToString());
}
}
private void LoadDropdowns()
{
this.StaffMembers = this.DA.GetStaffMembers();
}
}
public partial class JobRepeatViewModel : ViewModelBase
{
public void OnJobRepeatAnalysisSavePopUpClicked(object sender, RoutedEventArgs e)
{
if (this.OnSavePopUpClicked != null)
{
this.OnSavePopUpClicked(sender);
}
}
public void OnJobRepeatAnalysisCancelPopUpClicked(object sender, RoutedEventArgs e)
{
if (this.OnCancelPopUpClicked != null)
{
this.OnCancelPopUpClicked(sender);
}
}
public void Execute_SaveRepeatJobClick()
{
}
}
public partial class JobRepeatViewModel : ViewModelBase
{
public event JobRepeatAnalysisCancelPopupClicked_Event OnCancelPopUpClicked;
public delegate void JobRepeatAnalysisCancelPopupClicked_Event(object sender);
public event JobRepeatAnalysisSavePopupClicked_Event OnSavePopUpClicked;
public delegate void JobRepeatAnalysisSavePopupClicked_Event(object sender);
public MainWindowViewModel MainWindowViewModel
{
get { return Get(() => MainWindowViewModel); }
set { Set(() => MainWindowViewModel, value); }
}
public Job ParentJob
{
get { return Get(() => ParentJob); }
set { Set(() => ParentJob, value); }
}
public ACSDataAccess DA
{
get { return Get(() => DA); }
set { Set(() => DA, value); }
}
public ObservableCollection<Staff> StaffMembers
{
get { return Get(() => StaffMembers); }
set { Set(() => StaffMembers, value); }
}
}

Binding Silverlight controls collection to Grid - possible or not?

I'm a bit new to Silverlight and now I'm developing a map app. I have a collection of custom controls (map markers, POIs, etc). Every control has a property "Location" of the type Point, where Location.X means Canvas.Left of the control, Location.Ymeans Canvas.Top of the control.
I'm trying to refactor my interface to MVVM pattern. I want to do something like this:
Say my controls are in Canvas. I want to have something like:
<Canvas DataContext="{StaticResource myModel}" ItemsSource="{Binding controlsCollection}">
<Canvas.ItemTemplate> ... </Canvas.ItemTemplate>
</Canvas>
In my custom control I want to have something like:
<myCustomControl DataContext="{StaticResource myControlModel}" Canvas.Left="{Binding Location.X}" Canvas.Top="{Binding Location.Y}" />
Is it possible? Maybe there's a better way?
I would think it is possible to use the ItemsControl control for this.
Let's say you create a holder-control that holds the position of the control + more info that you choose.
I call it "ControlDefinition.cs":
public class ControlDefinition : DependencyObject, INotifyPropertyChanged
{
public static readonly DependencyProperty TopProperty = DependencyProperty.Register("Top", typeof(Double), typeof(ControlDefinition), new PropertyMetadata(0d));
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(Double), typeof(ControlDefinition), new PropertyMetadata(0d));
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(Object), typeof(ControlDefinition), new PropertyMetadata(null));
public Double Top
{
get { return (Double)GetValue(TopProperty); }
set
{
SetValue(TopProperty, value);
NotifyPropertyChanged("Top");
}
}
public Double Left
{
get { return (Double)GetValue(LeftProperty); }
set
{
SetValue(LeftProperty, value);
NotifyPropertyChanged("Left");
}
}
public Object Model
{
get { return (Object)GetValue(ModelProperty); }
set
{
SetValue(ModelProperty, value);
NotifyPropertyChanged("Model");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String aPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(aPropertyName));
}
}
Then, in my MODEL (ViewModel.cs), I create an ObservableCollection of this class:
public static readonly DependencyProperty ControlsProperty = DependencyProperty.Register("Controls", typeof(ObservableCollection<ControlDefinition>), typeof(MainWindow), new PropertyMetadata(null));
public new ObservableCollection<ControlDefinition> Controls
{
get { return (ObservableCollection<ControlDefinition>)GetValue(ControlsProperty); }
set
{
SetValue(ControlsProperty, value);
NotifyPropertyChanged("Controls");
}
}
Then, in the same MODEL, I initialize the collection and adds 4 dummy controls:
this.Controls = new ObservableCollection<ControlDefinition>();
this.Controls.Add(new ControlDefinition() { Top = 10, Left = 10, Model = "One" });
this.Controls.Add(new ControlDefinition() { Top = 50, Left = 10, Model = "Two" });
this.Controls.Add(new ControlDefinition() { Top = 90, Left = 10, Model = "Three" });
And I would have my VIEW (View.xaml) something like this:
<ItemsControl ItemsSource="{Binding Path=Controls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Beige" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Model, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Top, Mode=OneWay}" />
<Setter Property="Canvas.Left" Value="{Binding Path=Left, Mode=OneWay}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
NOTE that I show a "TextBlock" control inside my "DataTemplate", in lack of your control.
And I show the "Model" property (which I have defined as "String") in the TextBlock's "Text" property. You can assign the "Model" property to your control's "DataContext" property as in your example.
Hope it helps!
So, after all times of googling, asking and reading, here I found a solution:
There should be a model implementing INotifyPropertyChanged interface. DependencyProperties are not necessary. Here's an example:
public class MyItemModel: INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
...
//
private Point _location;
public Point Location
{
get { return Location; }
set { _location = value; NotifyPropertyChanged("Location"); }
}
// any other fields
...
//
}
Then, say we have a model with a collection of MyItemModels:
public class MyModel: INotifyPropertyChanged
{
// INotifyPropertyChanged implementation
...
//
private ObservableCollection<MyItemModel> _myCollection;
public ObservableCollection<MyItemModel> MyCollection
{
get { return _myCollection; }
set { _myCollection = value; NotifyPropertyChanged("MyCollection"); }
}
}
Then, in XAML, we should use ItemsControl like this:
<ItemsControl x:Name="LayoutRoot" DataContext="{StaticResource model}"
ItemsSource="{Binding MyCollection}"
HorizontalAlignment="Left" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="host"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="item" Background="Transparent">
<Grid.RenderTransform>
<TranslateTransform X="{Binding Location.X}" Y="{Binding Location.Y}"/>
</Grid.RenderTransform>
// other stuff here
...
//
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Works like a charm :) Thanks everyone.

Silverlight how to bind my usercontrol in Data Grid column template

today I getting crazy while trying to do, what I think, is simple thing.
I want to be able to create my usercontrol, and use it in my column template in my datagrid
I have searched and tried several combinations, and nothing appear to work
Can anyone help me?
public class User
{
public string Name { get; set; }
public bool IsValid { get; set; }
}
partial class MyControl : UserControl
{
private string _value;
public string Value
{
get { return _value; }
set { _value = value;
txt.Text = value;
}
}
public MyControl()
{
InitializeComponent();
}
}
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txt" Text="[undefined]"></TextBlock>
</Grid>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var items = new List<User>();
items.Add(new User{Name = "user 1", IsValid = true});
items.Add(new User { Name = "user 2", IsValid = false });
myGrid.ItemsSource = items;
}
}
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid x:Name="myGrid" AutoGenerateColumns="False" IsReadOnly="True">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn Header="Name">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<SilverlightApplication1:MyControl Value="{Binding Name}"></SilverlightApplication1:MyControl>
<!--<TextBlock Text="{Binding Name}"></TextBlock>-->
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
Edited:
I also tried the following, but I get no results on my grid:
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="txt" Text="{Binding Value}"></TextBlock>
</Grid>
public partial class MyControl : UserControl
{
public DependencyProperty ValueProperty =
DependencyProperty.Register("Value",
typeof(string),
typeof(MyControl),
new PropertyMetadata(OnValueChanged));
public string Value
{
get
{
return (string)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
NotifyPropertyChanged("Value");
}
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl) d).Value = (String)e.NewValue; //ERROR: here I got always empty string
}
public MyControl()
{
InitializeComponent();
}
}
The reason why your first code didn't work is simple. To be able to bind the "Value" property on your "MyControl" (Value={Binding Name}), it has to be a Dependency Property. which you fixed in your second bit of code.
Here's what I did (and that worked well):
<UserControl x:Class="BusinessApplication8_SOF_Sandbox.Controls.MyControl"
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"
d:DesignHeight="300" d:DesignWidth="400" Name="myControl">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Name="textBlock" Text="{Binding Value, ElementName=myControl}"/>
</Grid>
</UserControl>
For the rest, I used your code.
Another possibility, which should be OK in case you only want the data to flow in one direction ("One Way" from source to target), as it is the case when using the TextBlock control is to update the Text property in the "OnValueChanged". here's the code for the Value property:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyControl),
new PropertyMetadata("", OnValueChanged));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (MyControl)d;
var oldValue = (string)e.OldValue;
var newValue = target.Value;
target.OnValueChanged(oldValue, newValue);
}
protected virtual void OnValueChanged(string oldValue, string newValue)
{
textBlock.Text = newValue;
}
and you can remove the binding in xaml:
<TextBlock Name="textBlock" />
this worked for me as well.
Hope this helps ;)
you need to implement the INotifyPropertyChanged interface in your User class so that bound user controls are aware if any of the bound properties change. See the following page with the details how to implement it : http://www.silverlightshow.net/tips/How-to-implement-INotifyPropertyChanged-interface.aspx
As you can see you need to implement the interface and in the setters raise the event OnPropertyChanged
Then it should work with your bindings.
Best,
Tim

Combobox display value in Silverlight

I have ComboBox with CheckBoxes for items.
When user checks or uncheckes boxes I want the selected values to be displayed in the ContentPresenter separated by comma.
At the the moment I have overridden ContentPresenter:
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{StaticResource SelectedOperationsText}"/>
ContentPresenter is a part of ComboBox style by default.
Any hints on how to implement this feature?
ComboBox ItemTemplate is implemented like this:
<DataTemplate x:Key="ComboItemTemplate">
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>
This solution isn't ideal (for example, you can create custom control template for control inherited from combobox), but it works.
Xaml
<my:MyComboBox Width="180" ItemsSource="{Binding TestItems}" Text="{Binding SelectedItemsText}">
<my:MyComboBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Text}"/>
</Grid>
</DataTemplate>
</my:MyComboBox.ItemTemplate>
</my:MyComboBox>
Hack of the combobox:
public class MyComboBox : ComboBox
{
private ContentPresenter selectedContent;
public MyComboBox()
{
this.DefaultStyleKey = typeof(ComboBox);
}
public override void OnApplyTemplate()
{
this.selectedContent = this.GetTemplateChild("ContentPresenter") as ContentPresenter;
this.RefreshContent();
base.OnApplyTemplate();
this.SelectionChanged += (s, e) =>
{
//Cancel selection
this.SelectedItem = null;
this.RefreshContent();
};
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyComboBox),
new PropertyMetadata(null, new PropertyChangedCallback((s,e)=>((MyComboBox)s).RefreshContent())));
private void RefreshContent()
{
if (this.selectedContent != null)
{
var tb = (TextBlock)this.selectedContent.Content;
tb.Text = this.Text;
}
}
}
MainViewModel
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
this.InitializeTestItems();
}
public void InitializeTestItems()
{
this.TestItems = new List<TestItemModel>{
new TestItemModel{IsChecked=true, Text="first"},
new TestItemModel{IsChecked=false, Text="second"},
new TestItemModel{IsChecked=false, Text="third"}};
this.RefreshSelectedItemsText();
foreach (var item in this.TestItems)
item.CheckChanged += (s, e) => this.RefreshSelectedItemsText();
}
private void RefreshSelectedItemsText()
{
SelectedItemsText = string.Join(", ", this.TestItems.Where(ti => ti.IsChecked).Select(ti => ti.Text));
}
public List<TestItemModel> TestItems { get; set; }
private string selectedItemsText;
public string SelectedItemsText
{
get { return selectedItemsText; }
set
{
selectedItemsText = value;
OnPropertyChanged("SelectedItemsText");
}
}
}
4.ItemViewModel
public class TestItemModel
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (CheckChanged != null)
CheckChanged(this, null);
}
}
public string Text { get; set; }
public event EventHandler<EventArgs> CheckChanged;
}
I did not understand what you meant by "the ContentPresenter".
If you want a combox box, with the list of selected items as its text, I can explain how my son (who's not in SO) did it:
He put a grid with a ComboBox followed by a TextBlock. The ItemTemplate of the ComboBox includes a check box with a handler for the Checked and UnChecked events. In these events, he recomputed the Text property of the TextBlock, based on the selected state of the check boxes.
Here is the XAML:
<Grid Name="LayoutRoot">
<ComboBox ItemsSource="{Binding Path=SitesList}" Name="CBsites" DropDownOpened="ComboBox_DropDownOpened" DropDownClosed="ComboBox_DropDownClosed">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Location}" Checked="SiteCheckBox_Checked" Unchecked="SiteCheckBox_Unchecked" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Name="TXTselected" IsHitTestVisible="False" VerticalAlignment="Center" Margin="6,0,0,0" />
</Grid>
I think one can do it without the TextBlock. Hopefully, this can put you in the right direction.
I have created a codeplex project here: codeplex inspired by this blog and a number other ones, please check it out and improve it etc. Hopefully this or something like it will find it's way into the toolkit...
I prefer not needing a selection boolean in the bound data so i brought a bindable SelectedItems

Resources