So I'm working on a calculator, basically a copy of the Windows Version, as a training excercise. I have implemented a History of past calculations, and I was asked to transform this history from TextBox to Listview.
What I want to do is copy one of the past calculations back into the Calculator TextBox when I click on it, just like in the Windows Calculator.
My ListViewCode:
<ListView Grid.Column="0" Grid.Row="1" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseLeftButtonDown" Handler="RetrievePastCalculation" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And this is the RetrievePastCalculation method, but it doesn't work, nothing happens when I click on a ListViewItem. I'm new to WPF by the way.
private void RetrievePastCalculation(object sender, MouseButtonEventArgs e)
{
innerTextBox.Text = history.SelectedItems.ToString();
}
This is where I add items to the ListView I think, it's the Equal button method:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
history.Items.Add(innerTextBox.Text + "=" + textBox.Text);
innerTextBox.Clear();
}
history.SelectedItems is a collection, so calling ToString on it won't give you anything other than the name of the type. If you try it in the debugger (which you should), you'll see that it returns System.Windows.Controls.SelectedItemCollection. Now, at this point you can either fix your issue one of two ways: you can continue to use your current event-based approach, or you can use binding.
Events
With events, you can hook a handler to the Selected event for each ListItem that you add to the list:
private void ButtonEquals_Click(object sender, RoutedEventArgs e)
{
Calculator calculate = new Calculator();
textBox.Text = calculate.Calculate(innerTextBox.Text);
var item = new ListViewItem();
item.Content = innerTextBox.Text + "=" + textBox.Text;
item.Selected += HistoryItem_Selected //hooks the handler to the 'Selected' event
history.Items.Add(item);
innerTextBox.Clear();
}
then define the handler itself:
private void HistoryItem_Selected(object sender, RoutedEventArgs e)
{
// here 'sender' will be the ListItem which you clicked on
// but since it's an object we need to cast it first
ListViewItem listItem = (ListViewItem)sender;
// now all that's left is getting the text and assigning it to the textbox
innerTextBox.Text = listItem.Content.ToString();
}
Binding
Binding is much simpler as far as the amount of code is concerned, but has a steeper learning curve. Here, instead of setting the TextBox.Text property directly, we will specify a binding expression. This means that the value will always be the same as that of the bound expression.
<Window x:Class="WpfApplication1.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">
<Grid>
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Name="history" />
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
<Button Name="ButtonEquals" Content="equals" Click="ButtonEquals_Click"/>
</StackPanel>
</Grid>
</Window>
I've run this in a new WPF project and it works as expected: the text box displays whatever text is in the clicked item from the list.
One thing to note is that both solutions assume that you are assigning strings to the ListViewItem Content. As you may know, you can assign other controls or any object to the Content property of a UI Control (ListViewItem inherits from Control). That's why the ListViewItem.Add method takes an argument of type object and is not restricted to one of type string. If you assigned anything other than a string in your button click event handler, both of the two cases above would likely break.
You could bind the value of the TextBox to the SelectedItem of the ListView. Here's an example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" Background="Transparent"
HorizontalAlignment="Stretch" BorderThickness="0" Margin="10,10,10,0">
<ListViewItem>Calc1</ListViewItem>
<ListViewItem>Calc2</ListViewItem>
</ListView>
<TextBox Text="{Binding ElementName=history, Path=SelectedItem.Content}" />
</StackPanel>
</Page>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Grid.Column="0" Grid.Row="0" Foreground="#616161" Name="history" BorderThickness="1,1" Height="50" Width="200" SelectionChanged="history_SelectionChanged">
<ListViewItem>
<TextBlock> A ListView</TextBlock>
</ListViewItem>
<ListViewItem>
with several
</ListViewItem>
<ListViewItem>
items
</ListViewItem>
</ListView>
<TextBox Grid.Row="1" Text="{Binding ElementName=history,Path=SelectedValue.Content}"
BorderThickness="1,1" Height="50" Width="200" />
</Grid>
It's better if you do it using XAML code. try to select item 0 and 1 to see the difference and understand how listboxworks.
now replace the text of textbox binding with following:
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text}"
and seee the output for item 0. Hopefully you'll achieve desired output with a lot less effort.
Now that you have explained the whole problem i think you need to implement a converter in the text binding of TextBox. like below text
Text="{Binding ElementName=history,Path=SelectedValue.Content.Text,Converter={StaticResource mytextconverter}}"
and write down a logic to extract a part of text on the basis of '=' char. It's very easy to write a converter class. to write a converter follow the below link:
WPF Converter example
Related
I have a XAML UserControl embedded in a WinForms/WPF Interop ElementHost control. The control is pretty simple - it's just a dropdown with a button - here's the entire markup:
<UserControl x:Class="Rubberduck.UI.FindSymbol.FindSymbolControl"
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:local="clr-namespace:Rubberduck.UI.FindSymbol"
mc:Ignorable="d"
d:DesignHeight="27" d:DesignWidth="270">
<UserControl.Resources>
<local:DeclarationImageConverter x:Key="DeclarationImageConverter" />
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="local:FindSymbolControl.GoCommand"
Executed="CommandBinding_OnExecuted"
CanExecute="CommandBinding_OnCanExecute"/>
</UserControl.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True"
ItemsSource="{Binding MatchResults}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="IdentifierName">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:SearchResult">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Height="16" Width="16" Margin="2,0,2,0" Source="{Binding Declaration, Converter={StaticResource DeclarationImageConverter}}" />
<TextBlock Margin="2,0,2,0" Text="{Binding IdentifierName}" FontWeight="Bold" MinWidth="140" />
<TextBlock Margin="2,0,2,0" Text="{Binding Location}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1"
Command="local:FindSymbolControl.GoCommand">
<Image Height="16" Source="pack://application:,,,/Rubberduck;component/Resources/arrow.png" />
</Button>
</Grid>
</UserControl>
The problem is that it doesn't work reliably, and far from instinctively.
If I type something in the box that actually matches an item, nothing happens until I manually select that item in the dropdown. Like here, I typed "sleepD", the box autocompleted to "sleepDelay", but the command is still disabled:
Once I've selected the item in the dropdown, the command button gets enabled as expected (although the image on the button doesn't show up grayed-out when the button is disabled, so it's not exactly as obvious as I intended it to be).
(the screenshot isn't really showing it, but there's only 1 match for that search)
If I click the button at that point, it works as expected. The problem is that if I make a new selection from the dropdown after that, the text box gets cleared instead of displaying the item I selected, and there's a weird delay during which the box is displaying what appears to be selected whitespace - this only seems to happen when the previous selection was made after selecting a value in the dropdown while the search text matches multiple entries, like "Sleep" above.
After the box got cleared, I can make a new selection from the dropdown and it will work as expected (except the VBE won't actually activate the CodePane I'm setting the selection to, but that's a separate issue).
The command implementation simply raises a Navigate event that passes a Declaration to the code that owns the VM instance.
The Search method, for which I need to add a .Take(50) after the .Select, to limit the number of returned results and perhaps reduce the lag a bit:
private void Search(string value)
{
var lower = value.ToLowerInvariant();
var results = _declarations.Where(
declaration => declaration.IdentifierName.ToLowerInvariant().Contains(lower))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.Select(declaration => new SearchResult(declaration));
MatchResults = new ObservableCollection<SearchResult>(results);
}
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
Search(value);
}
}
private SearchResult _selectedItem;
public SearchResult SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private ObservableCollection<SearchResult> _matchResults;
public ObservableCollection<SearchResult> MatchResults
{
get { return _matchResults; }
set { _matchResults = value; OnPropertyChanged(); }
}
}
There's also an IValueConverter involved, that takes the Declaration in the SearchResult and switches on the declaration's DeclarationType enum to return a pack uri that points to the .png image to use in the dropdown list.
Aaah found it. It was all in the XAML.
Right here:
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
That line doesn't belong there; binding the TextSearch.Text property instead...
TextSearch.Text="{Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Makes it all work as intended. No glitch, no lag. Well there is a lag when I first drop the dropdown, but that's another issue.
Lesson learned: when TextSearch is enabled on an editable combobox, don't bind the Text property, unless you want weird behavior.
I have text boxes that are getting URL inside, when you put the URL (long) in it, I want it to go down one row in order to see the last character of the URL.How can I achieve it instead
of changing the width size?
The TextBlock class features the TextBlock.TextTrimming Property, which enables users to add an ellipsis (...) at the end of text that is too long to be displayed in the TextBlock. If your TextBox is not being used for text input, then you can simply use a TextBlock control instead.
If you really need to use a TextBox, then unfortunately that has no such property. One alternative is to use a custom TextBox that does have this property. You can find an example of that in the WPF TextBox With Ellipsis page on CodeProject.
UPDATE >>>
As you have not shown any code, nobody can tell you what you did wrong. Either way, this is a simple issue that I'm sure that you can fix yourself. Add this to a different view somewhere else:
<TextBlock Text="123456789012345678901234567890123456789012345678901234567890"
Width="100" TextTrimming="WordEllipsis" />
Now you should be able to see the ellipsis at the end of the TextBlock. That's how simple it is. If you example is not working, then you have made it not work by adding something else.
Try scrolling the text box to the beginning of the text when focus lost (not sure how to do that with data binding):
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
(sender as TextBox).ScrollToHome();
}
You can also create a Behavior to avoid direct event handling:
Add reference to System.Windows.Interactivity (installed with Expression Blend).
Add a Behavior class:
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfApplication2
{
public class AutoScrollToHomeBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
AssociatedObject.LostFocus += (tb, args) =>
{
(tb as TextBox).ScrollToHome();
};
}
}
}
Attach a Behavior to your text box:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Vertical">
<TextBox HorizontalAlignment="Left" Height="23" Width="120">
<e:Interaction.Behaviors>
<local:AutoScrollToHomeBehavior />
</e:Interaction.Behaviors>
</TextBox>
<TextBox HorizontalAlignment="Left" Height="23" Width="120">
<e:Interaction.Behaviors>
<local:AutoScrollToHomeBehavior />
</e:Interaction.Behaviors>
</TextBox>
</StackPanel>
</Grid>
</Window>
Hej
I want to create a standard holdevent. When you hold an element, there would appear some options you could chose like a new list.
How do you create this, is it just simply done with a popup or is there a smarter way?
Extra
After finding the answer, see answer below, some nice info is:
Put the context creation inside the hold event.
Then you can change to different contextmenus depending on the item. You can get the item that was holded by the following
private void StackPanel_Hold(object sender, GestureEventArgs e)
{
ItemViewModel itemViewModel = (sender as StackPanel).DataContext as ItemViewModel;
string t = itemViewModel.LineOne;
}
And
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Height="78" Hold="StackPanel_Hold">
<TextBlock Text="{Binding LineOne}" />
<TextBlock Text="{Binding LineTwo}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
A good link for easy implementation is also youtube link below, replicated here :
Youtube
A ContextMenu is one option..
http://blogs.msdn.com/b/msgulfcommunity/archive/2013/05/19/windows-phone-toolkit-context-menu-getting-selected-item-within-a-long-list-selector.aspx
I assume this has been asked before but I couldn't find it, so let me know if it's a duplicate found with other verbiage or something.
The problem is with an SL4 DataGrid which contains multiple CellTemplate's including Checkbox, Button etc. By default it will only tab through these elements on the first row. If I set TabNavigation="Cycle" it will tab through all the elements, but it doesn't move on to the next elements and instead just re-iterates the tabbing through the same DataGrid.
If I set it to Once then again it will only tab through the first row....and SL4 doesn't appear to have a Continue option to move onto the next object once it reaches the edge.
I'm looking for just an easy way to take the equivalent of TabNavigation="Cycle" except when it reaches the last tab-able element in the DataGrid then it moves on to the next thing in the tree instead of just tabbing back to the first element in the DataGrid again. What am I missing here?
There doesn't seem to be a native way to do this in Silverlight, here is a list of supported key strokes in the data grid control: http://msdn.microsoft.com/en-us/library/cc838112(v=vs.95).aspx
I was able to fake it by using a KeyDown event and checking for Tab then setting the editing cell manually:
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="Some text" />
<sdk:DataGrid Grid.Row="1" ItemsSource="{Binding People}" AutoGenerateColumns="False" KeyDown="DataGrid_KeyDown">
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn>
<sdk:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding LastName}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellEditingTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<TextBox Grid.Row="2" Text="Some more text" />
</Grid>
private void DataGrid_KeyDown(object sender, KeyEventArgs e)
{
DataGrid dg = (DataGrid)sender;
ObservableCollection<Person> items = dg.ItemsSource as ObservableCollection<Person>;
if (e.Key == Key.Tab && dg.SelectedIndex < items.Count -1)
{
dg.SelectedIndex++;
dg.CurrentColumn = dg.Columns[0];
dg.BeginEdit();
var cell = dg.CurrentColumn.GetCellContent(dg.SelectedItem);
}
}
I had some exp wth SL4 long time back. I will give your problem a try:
See the property you set to get your desired behavior won't work. It will be the Microsoft way only, thus alternative is to write your own code to achieve the required behavior.
My idea is to attach the following event to each datagrid cell:
private void DataGridCell_KeyDown(object sender, KeyEventArgs e)
{
if (keypressed == 'TAB' && last cell of the datagrid)
{
e.handled=true;
int tabIndex = dg.TabIndex;
tabindex++;
Control control = GetControl(tabIndex); // You can use visual tree in the method to get it
control.select();
control.focus();
}
}
My apologies I have written pseudo instead of real code, since it will take time for me to recall the code I use to do in SL.
Hope this solution works for you both way, when you tab out of data-grid and reverse tab in to the data-grid.
Is it possible to subscribe to a Property of a specific UIElement in WPF?
I want to animate an UIElement as soon as the Heightvalue changes and add the new height to a list, but I don't see how I can subscribe to the HeightProperty?
Samplecode:
Something like this:
MainWindow.xaml:
<Window x:Class="BibVisualization.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Background="Red" Width="30" Grid.Row="0" x:Name="myBorder">
<TextBlock Text="Really really long text with wrapping, but the wrapping changes based on border's width"
Width="{Binding ElementName=myBorder, Path=Width}"
TextWrapping="Wrap" />
</Border>
<Button Grid.Row="1" Height="10"
Content="Make border bigger" Click="OnButtonClick" />
</Grid>
</Window>
MainWindow.xaml.cs
private void OnButtonClick(Object sender, RoutedEventArgs e)
{
myBorder.Width += 10;
//Bind to textblock's actualheight and execute OnHeightChange?
}
private int accumulatedChange;
private void OnHeightChange(Object sender, SomeEventArgs? e)
{
accumulatedChange -= e.OldValue (if possible);
accumulatedChange += e.NewValue;
}
I think you can use the SizeChanged-Event of the FrameworkElement class to do what you want. All UIElements such as Button or Textblock derive from that class and therefore provide the event.
The SizeChangedEventArgs passed to registered method contains information if height or width has changed and provide the new values.
If I understood properly you'd want to 'bind' to ActualHeight ?
Take a look at this link (http://meleak.wordpress.com/2011/08/28/onewaytosource-binding-for-readonly-dependency-property/) - it describes how it can be done using attached properties basically.
Also take a look at this answer of mine from the other day, which basically describes very similar problem.
https://stackoverflow.com/a/15367642/417747
(use link there to download the support code - you can bind to via Style or as described in the article - it's all similar thing)
What you'd need is to bind to ActiveHeight using the method described in the article, that changes your view-model's MyHeight property - handle it's set to get when the active height changes. Let me know if any questions.
Hope it helps.
You could use the DependencyPropertyDescriptor to add a ValueChangedHandler for the property:
DependencyPropertyDescriptor descriptor=DependencyPropertyDescriptor.FromProperty(UIElement.HeightProperty,typeof(UIElement));
descriptor.AddValueChanged(myUIElementToWatch, new EventHandler(OnHeightOfUiElementChanged));