I am trying to create a custom control that will display a hyperlink button with some text below the link. The idea is to have urgent messages show up on a screen of a Silverlight page. From what I have read, I thought that I should be able to create a new control and then create some dependancy properties and bind the dynamic parts of the component pieces to them in order to allow me to add multiple instances of the custom control to my Silverlight project. Here is my XAML that defines the control
<UserControl
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"
x:Class="WhatsNew.UrgentStoryGridControl"
d:DesignWidth="608" d:DesignHeight="65" Background="White">
<UserControl.Resources>
<Style x:Key="WhatsNewTitleStyle" TargetType="HyperlinkButton">
Removed for Brevity
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Height="65" Margin="0" VerticalAlignment="Bottom" Background="White">
<StackPanel>
<HyperlinkButton Style="{StaticResource WhatsNewTitleStyle}" Content="{Binding linkText}" HorizontalAlignment="Left" VerticalAlignment="Top" NavigateUri="{Binding linkURI}" Foreground="Red"/>
<TextBlock Style="{StaticResource WhatsNewTextStyle}" Text="{Binding storyText}" Margin="0,13,0,0" d:LayoutOverrides="Height"/>
</StackPanel>
</Grid>
In the code behind, I have created three dependancy properties
Partial Public Class UrgentStoryGridControl
Inherits UserControl
Public Shared linkTextProperty As DependencyProperty = DependencyProperty.Register("linkText", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("Link Text"))
Public Shared linkURIProperty As DependencyProperty = DependencyProperty.Register("linkURI", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("link.html"))
Public Shared storyTextProperty As DependencyProperty = DependencyProperty.Register("storyText", GetType(String), GetType(UrgentStoryGridControl), New PropertyMetadata("Story Text"))
Public Property linkText() As String
Get
Return GetValue(linkTextProperty)
End Get
Set(ByVal value As String)
SetValue(linkTextProperty, value)
End Set
End Property
Public Property linkURI() As String
Get
Return GetValue(linkURIProperty)
End Get
Set(ByVal value As String)
SetValue(linkURIProperty, value)
End Set
End Property
Public Property storyText As String
Get
Return GetValue(storyTextProperty)
End Get
Set(ByVal value As String)
SetValue(storyTextProperty, value)
End Set
End Property
End Class
When I place this control on my Silverlight project using Expression Blend, I see the three properties listed in the Miscellaneous section of the properties window as I would expect. The values from the PropertyMetadata are populated as the default values for these properties. Here is the code from my Silverlight project where I leave the default values alone:
<local:UrgentStoryGridControl x:Name="urgentStory" Height="65" />
Here is the code where I try to set the values to something:
<local:UrgentStoryGridControl x:Name="urgentStory" Height="65" linkText="Test Link Text" linkURI="testpage.html" storyText="Sample Story Text" />
Either way I attempt to use the control, I'm not getting anything displayed when I launch the application. I figure that I'm missing something small but after having spent a lot of time today researching this, I'm not finding anything that would indicate what I'm missing or doing wrong.
You need to set the DataContext in your custom UserControl or else your bindings won't work.
In your UrgentStoryGridControl's constructor, you should be able to set Me.DataContext = Me
Related
I'm using the Microsoft ribbon library: System.Windows.Controls.Ribbon.
I know this question looks big, but what I'm trying to do is actually not that complicated, there are just a few pieces involved.
The Goal
I'm trying to bind the selection of a RibbonComboBox to a property of one of my classes, which I'll call TestBindingSource, but I need to be able to cancel the change of selection. So if they select an item from the RibbonComboBox, but then cancel that change, the selection needs to stay as it was.
The items being show in the RibbonComboBox represent members of an Enum, which I'll call TestEnum. I built another class TestEnumGalleryItem to represent the TestEnum values in the RibbonComboBox and I use the RibbonGallery.SelectedValue and RibbonGallery.SelectedValuePath to bind to the property on TestBindingSource. In case that was too hard to follow, you should be able to see what I mean from my code.
The Code
The below is a simplified version of my real code, try not to dock me too many points for style. I've tested this in a new project and it can be used to show the problem I'm running into. Remember to add a reference to the Microsoft ribbon library.
MainWindow.xaml
<Window x:Class="MainWindow"
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"
xmlns:local="clr-namespace:VBTest"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Ribbon>
<RibbonTab Header="Test">
<RibbonGroup>
<RibbonComboBox Name="TestComboBox">
<RibbonGallery Name="TestGallery" MaxColumnCount="1" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectedValuePath="EnumValue" SelectedValue="{Binding BindingSource.TestEnumValue}">
<RibbonGallery.ItemsSource>
<x:Array Type="local:TestEnumGalleryCategory">
<local:TestEnumGalleryCategory/>
</x:Array>
</RibbonGallery.ItemsSource>
<RibbonGallery.CategoryTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<RibbonGalleryItem ToolTipTitle="{Binding EnumName}" ToolTipDescription="{Binding EnumDescription}">
<TextBlock Text="{Binding EnumName}" Margin="0, -3, -0, -3"/>
</RibbonGalleryItem>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</RibbonGallery.CategoryTemplate>
</RibbonGallery>
</RibbonComboBox>
<RibbonButton Label="Break" Click="RibbonButton_Click"/>
</RibbonGroup>
</RibbonTab>
</Ribbon>
</Grid>
</Window>
MainWindow.xaml.vb
Imports System.ComponentModel
Class MainWindow
Public Sub New()
BindingSource = New TestBindingSource
InitializeComponent()
End Sub
Public Property BindingSource As TestBindingSource
Get
Return GetValue(BindingSourceProperty)
End Get
Set(ByVal value As TestBindingSource)
SetValue(BindingSourceProperty, value)
End Set
End Property
Public Shared ReadOnly BindingSourceProperty As DependencyProperty =
DependencyProperty.Register("BindingSource",
GetType(TestBindingSource), GetType(MainWindow))
Private Sub RibbonButton_Click(sender As Object, e As RoutedEventArgs)
Stop
End Sub
End Class
Public Enum TestEnum
ValueA
ValueB
End Enum
Public Class TestEnumGalleryCategory
Public Property Items As New List(Of TestEnumGalleryItem) From {New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueA, .EnumName = "Value A", .EnumDescription = "A's description"},
New TestEnumGalleryItem With {.EnumValue = TestEnum.ValueB, .EnumName = "Value B", .EnumDescription = "B's description"}}
End Class
Public Class TestEnumGalleryItem
Public Property EnumValue As TestEnum = TestEnum.ValueA
Public Property EnumName As String
Public Property EnumDescription As String
End Class
Public Class TestBindingSource
Implements INotifyPropertyChanged
Private _TestEnumValue As TestEnum = TestEnum.ValueA
Property TestEnumValue As TestEnum
Get
Return _TestEnumValue
End Get
Set(value As TestEnum)
'Don't actually set new value, just leave it the same to simulate cancelation
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(TestEnumValue)))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class
The Problem
You'll see when you run the code that the RibbonComboBox displays "Value A" by default. Change the selection to "Value B". The selection of the RibbonComboBox changes and now displays "Value B". This is not what I want to happen, the selection should immediately change back to "Value A".
If you look at the code for TestBindingSource.TestEnumValue, you'll see that I don't actually keep the new value when set, instead I leave the old one to simulate the user canceling the change. I then raise the PropertyChanged event to update the UI so it knows what the real value of the property is.
After you've changed to "Value B", click the "Break" button (included for convenience). In the Visual Studio watch window, compare the values of TestGallery.SelectedItem and TestGallery.SelectedValue. You'll see that TestGallery.SelectedValue holds the correct TestEnum value ValueA. Now look at TestGallery.SelectedItem and you'll see that it still holds the item representing ValueB.
So even though the RibbonGallery has been properly informed that the value should now be ValueA, it's still showing ValueB. How can I fix that?
I'll level with you, I don't have a ton of time to spend on this bug, and I'm used to having to make hacky workarounds when it comes to the ribbon. Any solution you can give me on how to get the
RibbonGallery (and hence the RibbonComboBox) to update correctly will be appreciated.
After more testing and research, I realized that this problem is not unique to the ribbon library. It actually seems to be a problem with the normal ComboBox too and presumably all ItemsControls. Once I realized that, I was able to search for an answer more effectively and found the solution here:
https://nathan.alner.net/2010/04/25/cancelling-selection-change-in-a-bound-wpf-combo-box/
It's not a perfectly clean solution, but in my specific case setting the value to the new selection and then immediately setting it back doesn't cause any problems, so that's what I did. For the record, when setting the selection back, I used DispatcherPriority.DataBind instead of DispatcherPriority.ContextIdle, this way the change never even shows in the UI but the solution still works.
So, I have a UserControl named ApplicationForm. It has some TextBoxes and a Button. Each TextBox has a Binding. An example of it:
<xctk:WatermarkTextBox Watermark="PACAPIME" Text="{Binding NumPac}" Grid.Column="2" Grid.Row="2" />
Then, my View is bound to a ViewModel like this:
<UserControl x:Class="Verkooporder"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vm="clr-namespace:Koala" mc:Ignorable="d" d:DesignHeight="720" d:DesignWidth="1100">
<UserControl.Resources>
<vm:VerkooporderViewModel x:Key="VerkooporderViewModelDataSource"
d:IsDataSource="True"/>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource VerkooporderViewModelDataSource}"/>
</UserControl.DataContext>
The corresponding property in my ViewModel is like this:
Private _oNumPac As String = ""
Public Property NumPac As String
Get
Return _oNumPac
End Get
Set(ByVal value As String)
_oNumPac = value
RaisePropertyChanged()
End Set
End Property
This is working great inside my UserControl. Now, I call my UserControl in my MainWindow:
<local:Verkooporder />
I want to be able to access the Property NumPac like <local:Verkooporder NumPac="" /> but this isn't working.
Also, how can I have access to the Button Command?
Public Shared _oNumPac As DependencyProperty = DependencyProperty.Register("NumPac",
GetType(String), GetType(VerkooporderViewModel), New PropertyMetadata(""))
Public Property NumPac As String
Get
Return GetValue(_oNumPac)
End Get
Set(value As String)
SetValue(_oNumPac, value)
End Set
End Property
But it doesn't show up when I try to type this:
<local:Verkooporder NumPac="Test" />
Notice that the properties are in the ViewModel of the UserControl, not the code behind View
I have a textblock, and I want to display a text with a defined string. How to do it?
The textblock:
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Padding="6" VerticalAlignment="Center" Margin="45,0,0,0" Height="30" Width="386" Text="My Program ver. Version"/>
My string:
Public Version As String = "1.0a"
You can use StringFormat.
<TextBlock Text="{Binding Path=Version, StringFormat=My Program ver. {0}}" />
In your code- behind you must change Version to property (this property should be ReadOnly because it isn't change in runtime) and assign DataContext in constructor:
Class MainWindow
Public Sub New()
InitializeComponent()
Me.DataContext = Me
End Sub
ReadOnly Property Version As String
Get
Return "1.0a"
End Get
End Property
End Class
If you want your TextBlock to update the version number every time you have a new version,
you can do it like this in C#. You can probably find out easily how to write this in VB.
This will update your TextBlock everytime you publish a new version of your program.
In XAML you bind a TextBlock text to "Version":
<TextBlock Text="{Binding Version, Mode=OneWay}" />`
And then in code-behind or in your view-model you can use a property for that Binding that you have in the XAML TextBlock:
public string Version
{
get
{
return String.Format("VERSION: {0}",DeploymentInfo.Version.ToString());
}
}
Then you need to add a reference to "System.Deployment" in your project.
This will only work when you have done a "PUBLISH" of your project. When you start the debugger you will probably only see version number: 0.0.0.0
In XAML file:
First you should name your TextBlock i have gave tbWithNoName for example.
<TextBlock x:Name="tbWithNoName" HorizontalAlignment="Left" TextWrapping="Wrap" Padding="6" VerticalAlignment="Center" Margin="45,0,0,0" Height="30" Width="386" Text="My Program ver. Version"/>
Then add Loaded call on Window object.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
Loaded="Window_Loaded">
Insert Window_Loaded function to your vb file.
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
tbWithNoName.Text = tbWithNoName.Text + " " + Version
End Sub
This will change TextBlock's Text when Window loaded
in my Silverlight 4 app, I try to create a simple UserControl, which will be consumed by my Application. To keep things simple, it shall have a "header" and a placeholder, where I want to place any kind of control.
<User Control ...>
<Grid x:Name="LayoutRoot">
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title}" />
<ContentPresenter x:Name="ContentPresenterObject" />
</Grid>
</UserControl>
In the code behind, I have created a property for the text of the TextBlock
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyAccordion), null);
This way, I can set the Title property, when I use the Control in my application.
<local:MyAccordion Title="Test"/>
But it seems, that the binding at the textblock Text="{Binding Title}" doesn't make the text "Test" to be displayed as the textblocks text.
My question is: How can I make the Property Title to be displayed as the textboxes text and how do I do this for the - any type of user control containable - contencontrol?
Thanks in advance,
Frank
Maybe DataContext of control or page was not set. - First of all you should read more about a Binding ("http://www.silverlight.net/learn/data-networking/binding/data-binding-to-controls-(silverlight-quickstart)"). If you are working on real project and will design a some arhitecture, you should read about MVVM pattern.
The answer is ElementPropertyBinding. I need to reference the User Control in the Binding or add the binding in the constructor.
Create the binding in XAML:
<User Control ... x:Name="userControl">
...
<TextBlock x:Name="TextBlockHeader" Text="{Binding Title, ElementName=userControl}" />
</UserControl>
Create the binding in the constructor (Code behind)
public MyUserControl()
{
// Required to initialize variables
InitializeComponent();
TextBlockHeader.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding() { Source = this, Path = new PropertyPath("Title") });
}
I still need to find out how to add a child control, but that's another question.
I'm using a the WPF DataGrid from the wpf toolkit and a TimePicker from AvalonControlsLibrary to insert a collection of TimeSpans. My problem is that bindings are not working inside the DataGrid, and I have no clue of why this isn't working.
Here is my setup:
I have the following XAML:
<Window x:Class="TestMainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wpf="http://schemas.microsoft.com/wpf/2008/toolkit" xmlns:a="http://schemas.AvalonControls/AvalonControlsLibrary/Controls" SizeToContent="WidthAndHeight" MinHeight="250" MinWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0">
<GroupBox.Header>
Testing it:
</GroupBox.Header>
<wpf:DataGrid ItemsSource="{Binding Path=TestSpans}" AutoGenerateColumns="False">
<wpf:DataGrid.Columns>
<wpf:DataGridTemplateColumn Header="Start">
<wpf:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<a:TimePicker SelectedTime="{Binding Path=Span, Mode=TwoWay}" />
</DataTemplate>
</wpf:DataGridTemplateColumn.CellEditingTemplate>
<wpf:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Span}" />
</DataTemplate>
</wpf:DataGridTemplateColumn.CellTemplate>
</wpf:DataGridTemplateColumn>
</wpf:DataGrid.Columns>
</wpf:DataGrid>
</GroupBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1">
<a:TimePicker SelectedTime="{Binding Path=SelectedTime, Mode=TwoWay}" />
</StackPanel>
</Grid>
And this is my ViewModel:
Imports System.Collections.ObjectModel
Public Class TestMainWindowViewModel
Private _selectedTime As TimeSpan = DateTime.Now.TimeOfDay
Public Property SelectedTime() As TimeSpan
Get
Return _selectedTime
End Get
Set(ByVal value As TimeSpan)
_selectedTime = value
End Set
End Property
Private _testSpans As ObservableCollection(Of TimeSpanContainer) = New ObservableCollection(Of TimeSpanContainer)
Public Property TestSpans() As ObservableCollection(Of TimeSpanContainer)
Get
Return _testSpans
End Get
Set(ByVal value As ObservableCollection(Of TimeSpanContainer))
_testSpans = value
End Set
End Property
Public Sub New()
_testSpans.Add(DateTime.Now.TimeOfDay)
_testSpans.Add(DateTime.Now.TimeOfDay)
_testSpans.Add(DateTime.Now.TimeOfDay)
End Sub
End Class
Public Class TimeSpanContainer
Private _span As TimeSpan
Public Property Span() As TimeSpan
Get
Return _span
End Get
Set(ByVal value As TimeSpan)
_span = value
End Set
End Property
Public Sub New(ByVal t As TimeSpan)
_span = t
End Sub
End Class
I'm starting this window in application.xaml.vb like this:
Class Application
' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
' can be handled in this file.
Protected Overrides Sub OnStartup(ByVal e As System.Windows.StartupEventArgs)
MyBase.OnStartup(e)
Dim window As TestMainWindow = New TestMainWindow
window.DataContext = New TestMainWindowViewModel()
window.Show()
End Sub
End Class
EDIT 1: I forgot to mention that the binding to SelectedTime TimeSpan works as expected. The problem are the bindings inside the DataGrid.
EDIT 2: Changed example a little bit to show the problem better.
What do you mean by your bindings aren't working? Are you getting no value in the timepicker control when you attempt to edit the value?
Edit:
Ok, I was having this same issue yesterday and I think there is 2 parts to the issue.
If there is no value appearing in the TimePicker control when you switch to edit mode then there is probably a binding issue with the control.
The binding to the underlying value I found to be an issue with using the DataGridTemplateColumn. Basically the grid doesnt handle your databinding back using the same mechanisms of regular bound columns. What it means is you need to perform the following binding on your controls within the column:
SelectedTime="{Binding Span, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
This will fix the binding back to the underlying object. However if there is still an issue with the control it may not help you much. Sorry but I haven't used AvalonControlsLibrary so not sure if there is a potential problem there. Fixing step 2 solved my issues.
Cheers
-Leigh
I know this is an old question, but I where playing with this exact control having the exact same issue. I looked in the TimePicker class of AvalonControlsLibrary and the constructor looks like this
/// <summary>
/// Default constructor
/// </summary>
public TimePicker()
{
SelectedTime = DateTime.Now.TimeOfDay;
}
Removing the line setting the SelectedTime restored the databinding behaviour for me making your posted example work as intended.