Hyperlink - content binding - c#

I have a TextBox where a user inputs a uri. This then becomes the NavigateUri property of a hyperlink, allowing the user to click on the link to open the page.
<!-- Input TextBox -->
<TextBox x:Name="linkBox" Width="175" Text="{Binding Path=DocRef, Mode=TwoWay}" />
<!-- Hyperlink -->
<TextBlock>
<Hyperlink DataContext="{Binding ElementName=linkBox}" NavigateUri="{Binding
Path=Text}" RequestNavigate="Hyperlink_RequestNavigate">
<TextBlock DataContext="{Binding ElementName=linkBox}"
Text="{Binding Path=Text}" />
</Hyperlink>
</TextBlock>
This works for inputting the whole (absolute) uri in the TextBox. However, the user wants to only input the 'document.extn' bit of the Uri, and have the application prepend the rest of the resource (ie, the 'http://www.example.com/' bit). How do I set the base part of the uri and append the document reference (preferably in xaml)? I came across Hyperlink's BaseUri property which sounds perfect, but unfortunately is protected, so this doesn't work:
<Hyperlink DataContext="{Binding ElementName=linkBox}"
BaseUri="http://www.example.com/" NavigateUri="{Binding Path=Text}"
RequestNavigate="Hyperlink_RequestNavigate">
Can anybody assist?

You may be able to use MultiBinding to join the 2 strings you need
<Hyperlink DataContext="{Binding ElementName=linkBox}" RequestNavigate="Hyperlink_RequestNavigate">
<Hyperlink.NavigateUri>
<MultiBinding StringFormat="{}{0}{1}">
<Binding FallbackValue="http://www.example.com/" />
<Binding Path="Text" />
</MultiBinding>
</Hyperlink.NavigateUri>
</Hyperlink>

You can create a Custom Converter using IValueConverter interface to get the base uri appended uri.

Related

binding to an object on a data template [duplicate]

I define a headertemplate into a wpf groupbox and the databinding doesn't work. I don't understand why.
<GroupBox>
<GroupBox.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="/PopuAssuNetApplication.UI.Control;component/Images/Members.png" Width="24" />
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{x:Static Member=resx:Resources.PersonsInContractGroupBox}">
<Binding Path="CurrentContract.Federation" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
<Binding Path="CurrentContract.Number" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
</Binding>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<WpfComponent:WaitControl Margin="7,0,0,0" VerticalAlignment="Top" Width="24" Height="24" MarginCenter="4">
<WpfComponent:WaitControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="true">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsMembersOfContractBusy, UpdateSourceTrigger=PropertyChanged, ElementName=PersonsInContract}" Value="false">
<Setter Property="WpfComponent:WaitControl.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</WpfComponent:WaitControl.Style>
</WpfComponent:WaitControl>
</StackPanel>
</DataTemplate>
</GroupBox.HeaderTemplate>
The problem is that the HeaderTemplate is used for templating the Header thus within the HeaderTemplate your DataContext is whatever you bind or assign to the Header property of your GroupBox.
Think of the Header property as almost like the DataContext for the header of the control. Normally the DataContext property inherits its value from its parent but since not every control has a Header the Header is blank unless you set it.
By binding your Header explicitly to the current DataContext Header="{Binding}" your example should work as you expect. To help illustrate how this works I've created a simple example below that shows how the Header and DataContext work independently from each other for providing data to either the body or header of the control.
<GroupBox Header="HEADER TEXT" DataContext="BODY TEXT">
<GroupBox.HeaderTemplate>
<DataTemplate>
<Button Content="{Binding}"
Background="LightGreen" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center" Content="{Binding}" />
</GroupBox>
This will yield a GroupBox that looks like the following.
I think that by default in databinding, wpf always gets data from the DataContext property. Seems not in datatemplate
Your assumption is correct about DataContext and it does work in the DataTemplate as I've demonstrated it's just that in the Header's template the DataContext is the value from the Header Property and not the DataContext itself.
The GroupBox does not have a member called "CurrentContract". Most probably, you want to accesss a property called "CurrentContract" from the corresponding ViewModel?! The ViewModel is the GroupBox's DataContext, so you have to change the Binding Paths to something like...
<Binding Path="DataContext.CurrentContract.Type" Converter="{StaticResource contractTypeConverter}" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}">
<GroupBox >
<GroupBox.HeaderTemplate>
<DataTemplate>
<RadioButton Content="myR"
IsChecked="{Binding rIsChecked, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}}" />
</DataTemplate>
</GroupBox.HeaderTemplate>
<GroupBox.Content>
<Grid IsEnabled="{Binding rIsChecked}">
</Grid>
</GroupBox.Content>
</GroupBox>
Just propagate the GroupBox DC to the DataTemplate content...works like a charm...
The lesson learned above is useful in general for DataTemplates, but I actually found out recently there is a better way to change the header of a groupbox:
<GroupBox>
<GroupBox.Header>
<CheckBox IsChecked="{Binding Path=mSomeBoolean}"/>
</GroupBox.Header>
</GroupBox>
This way there is no need to define a relative source in the bindings.
Also please note this issue with GroupBoxes and the header.
This is what worked for me:
<HeaderedContentControl Header="{Binding}" Style="{StaticResource TallHeaderedContentStyle}">
<HeaderedContentControl.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=HeaderText"} />
</DataTemplate>
</HeaderedContentControl.HeaderTemplate>

WPF Group Box Multibinding in Header

I would like to implement a multi binding for the header of my group box.
This is my current approach:
<GroupBox>
<GroupBox.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}My Custom Header - {0}">
<Binding Path="VM.Obj1.Obj2.PropertyName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</GroupBox.Header>
</GroupBox>
If I implement it in this way, the header of my group box shows System.Windows.Controls.TextBlock
What is the correct way to use multi binding for the header of my group box so that the Text Block uses multi binding and binds to my strig property "PropertyName"? So the result shall be:
My Custom Header - StringValueOfPropertyName
You don't need a multibinding when there's only one binding:
<GroupBox>
<GroupBox.Header>
<TextBlock Text="{Binding VM.Obj1.Obj2.PropertyName, StringFormat=My Custom Header - {0}}"/>
</GroupBox.Header>
</GroupBox>
In fact GroupBoxHeader allows string format directly using HeaderStringFormat:
<GroupBox Header="{Binding VM.Obj1.Obj2.PropertyName}"
HeaderStringFormat="My Custom Header - {0}" />

How do I access elements in a WPF popup?

I want to change the color of a button in a popup based on certain conditions and I want to set some text based on those conditions. I need to do this in the code behind.
I have a popup with several TextBlocks in a StackPanel. The first 3 are bound to details about the course (this is a scheduling app; school project). The last one I want to be empty unless there is a conflict concerning that course. That is, I want to dynamically decide what, if anything, goes in the TextBlock each time the popup is opened.
<Popup Name="CourseListDetail" Width="Auto" Height="Auto">
<StackPanel>
<TextBlock Margin="10,10,10,0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="CourseCode"/>
<Binding Path="LongTitle"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Margin="10,0,10,0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}/{2}">
<Binding Path="ProfessorsString"/>
<Binding Path="Enrollment"/>
<Binding Path="Capacity"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Margin="10,0,10,0" Grid.Row="2" Grid.Column="1" Text="{Binding MeetingsString}" TextWrapping="Wrap"
HorizontalAlignment="Center"/>
<TextBlock TextWrapping="WrapWithOverflow" MaxWidth="300" Text="{Binding Description}" Margin="10,10,10,10"/>
<TextBlock Name="ConflictText" Foreground="Red" HorizontalAlignment="Center" Text="{Binding ConflictString}"/>
<Button Name="Detail_AddCourse" Content="Add To Schedule" Margin="10,10,10,10" Padding="5" Background="LightGreen"
Click="AddCourseButton_Click"
Style="{StaticResource MyButtonStyle}"/>
</StackPanel>
</Border>
</Popup>
I have a function that opens the popup when you click on a course and that gives the popup the DataContext about the course, but I don't know how to access the TextBlock, or the button immediately below it, through the function. I figured there'd be a child property or something so I could call the button, something like:
CourseListDetail.Detail_AddCourse.Background = "Red";
or
CourseListDetail.Child.Button().Background = "Red";
etc.
Code behind function:
private void CourseListItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
ListViewItem selection = sender as ListViewItem;
Course course = selection.DataContext as Course;
CourseListDetail.DataContext = course;
CourseListDetail.PlacementTarget = selection;
CourseListDetail.IsOpen = true;
CourseListDetail.Focus();
hasConflict conflictType = _schedule.HasConflict(course);
if (conflictType != hasConflict.NO_CONFLICT) { //If there is a conflict
//Change button color to red here
if (conflictType == hasConflict.COURSE_FULL) { //If the course is full
//Set TextBlock text to conflict message here
}
}
else { //No conflict
//Set button color to green
}
}
hasConflict is just an enum
Use x:Name instead of Name. Then you will be able to access the element in the code behind. See In WPF, what are the differences between the x:Name and Name attributes? for an explanation.

WPF - How to display a tooltip on a hyperlink only when the hyperlink is disabled

This is a more specific description of my problem from a previous question, with a follow-up answer.
I had a standard hyperlink defined in XAML:
<TextBlock>
<Hyperlink IsEnabled="{Binding LinkEnabled}">
<TextBlock Text="{Binding Text}"/>
</Hyperlink>
</TextBlock>
The hyperlink's IsEnabled property is bound to a property on the view model, the value of which can change. I needed to place a tooltip on the hyperlink which would only be displayed if the hyperlink is disabled.
To show the tooltip only when the hyperlink is disabled, the ToolTipService.ShowOnDisabled and ToolTipService.IsEnabled (with the negation converter) properties must be set on the hyperlink:
<TextBlock>
<Hyperlink IsEnabled="{Binding LinkEnabled}"
ToolTip="ToolTip"
ToolTipService.ShowOnDisabled="True"
ToolTipService.IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={StaticResource negateConverter}}">
<TextBlock Text="{Binding Text}"/>
</Hyperlink>
</TextBlock>
However, the tooltip won't be shown, since once the hyperlink is disabled, it stops being hit-testable, because it is contained in the TextBlock (or so I understood).
Thus, the solution would be to change the "IsEnabled" property on the parent TextBlock, not on the hyperlink.
However, this works:
<TextBlock IsEnabled="False">
But this doesn't:
<TextBlock IsEnabled="{Binding LinkEnabled}">
In the latter case, changing the TextBlock's IsEnabled property will not change the hyperlink's IsEnabled property. To solve this issue, the hyperlink's IsEnabled property must be bound to the parent's property.
And here is the actual solution, all put together:
<TextBlock IsEnabled="{Binding LinkEnabled}">
<Hyperlink IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type FrameworkElement}}}"
ToolTip="ToolTip"
ToolTipService.ShowOnDisabled="True"
ToolTipService.IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type FrameworkElement}}, Converter={StaticResource negateConverter}}">
<TextBlock Text="{Binding Text}"/>
</Hyperlink>
</TextBlock>

Add brackets to text box value

I want to know if there is option that when I type into a text box or drag to anything (I'm using D&D functionality), the text on it will automatically insert brackets. I don't want to do that on the logic or in the code beyond just in the ui. Is that posible?
For example: if I type AAA, I will see in the text box (AAA).
I dont want to do that on the logic or in the code beyond
By your conditions it is not possible. Something has to capture the change event and add the brackets to the text. That something is not possible without logic as found in code behind.
The options are
Subscribe to the textblock's SelectionChange event and add the brackets.
Create a custom control which does #1 internal so the consumer doesn't have to do it. (By a technicality it answer's your question).
Put the textblock control between two labels which have the brackets as their context. Bind their visibility to a Boolean on the VM which reports when the bound data of the textblock has changed. If there is text then they become visible, if there is no text it is hidden. Downside is that this is not caught as the user types or until it fully changed, only when exiting the control.
Here is #3
<Label Content="(" Visibility="{Binding HasText, Converter={StaticResource WindowsVisibilityBooleanConverter}}" />
<TextBox Text="{Binding TextInput}"
Height="18"
HorizontalAlignment="Stretch" />
<Label Content=")" Visibility="{Binding HasText, Converter={StaticResource WindowsVisibilityBooleanConverter}}" />
Without any of your own logic code, I suppose this is the closest thing to what you want.
<TextBox x:Name="tbInput" />
<TextBlock Text="{Binding ElementName='tbInput', Path=Text, StringFormat={}({0})}" />
The downside would be that you'll always see the empty brackets () if the TextBox is empty.
See: String format using MultiBinding?
<StackPanel>
<Slider x:Name="sl1" Minimum="10" Maximum="100"/>
<Slider x:Name="sl2" Minimum="10" Maximum="100"/>
<Label x:Name="label13" Background="Yellow" Foreground="Black">
<Label.Content>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} x {1} Test">
<Binding ElementName="sl1" Path="Value" />
<Binding ElementName="sl2" Path="Value" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Label.Content>
</Label>
</StackPanel>

Categories