WPF ItemsControl - Change focus when item added - c#

I have an ObservableCollectiong<StringWrapper> (StringWrapper per this post) named Paragraphs bound to an ItemsControl whose ItemTemplate is just a TextBox bound to StringWrapper.Text.
XAML
<ItemsControl Name="icParagraphs" Grid.Column="1" Grid.Row="7" ItemsSource="{Binding Path=Paragraphs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<StackPanel Orientation="Vertical">
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Name="tbParagraph" TextWrapping="Wrap" AcceptsReturn="False" Text="{Binding Path=Text}" Grid.Column="0" KeyUp="tbParagraph_KeyUp" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#
public class StringWrapper
{
public string Text { get; set; }
public StringWrapper()
{
Text = string.Empty;
}
public StringWrapper(string text)
{
Text = text;
}
}
I'm trying to make it so when I press enter in a TextBox, I insert a StringWrapper in my ObservableCollection after the StringWrapper bound to the TextBox that's currently focused, which generates a new TextBox. So far, my code does this, though there are a couple glitches to work out.
My question is, how do I then set the focus to the newly generated TextBox? As far as I can tell, the control generation happens after the function that inserts the string returns.
I looked for something like an ItemsControl.ItemsSourceChanged event, but, at least, that name doesn't exist. I also tried attaching a handler to ObservableCollection.CollectionChanged, but that too seemed to fire before the TextBox was generated. Last, since the ItemsControl.Template is a StackPanel, I looked for a StackPanel.ControlAdded event, but couldn't find that either.
Ideas? Thanks!

You may have to handle CollectionChanged and then schedule the focus action to occur in the future using Dispatcher.BeginInvoke with a priority of Loaded. That should give the ItemsControl an opportunity to generate a container and perform layout.

Related

Binding a Text property for filtering within a popup that is part of a DataTemplate not working

I have an application that displays a datagrid. However the data has gotten big and I want to incorporate filters to some of the rows. I've gotten as far as creating a DataTemplate for my headers:
<DataGrid>
<DataGrid.Resources>
...
<DataTemplate x:Key="HeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentControl Content="{Binding}" VerticalAlignment="Center"/>
<ToggleButton Name="FilterButton" Grid.Column="1" Content="▼" Margin="2, 1, 1, 1" Padding="1, 0"/>
<Popup IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}" PlacementTarget="{Binding ElementName=FilterButton}" StaysOpen="False">
<Border Background="White" Padding="3">
<TextBox Text={Binding PetNameFilterSearchBox, Mode=TwoWay} Width="300"/> <!--The Text Box I want to bind-->
</Border>
</Popup>
</Grid>
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Width="6*" Header="Pet Name" Binding="{Binding PetName}" ElementStyle="{DynamicResource DataGridTextColumnWrap}" HeaderTemplate="{StaticResource HeaderTemplate}"/>
</DataGrid.Columns>
</DataGrid>
So far what it does is show a button next to the header text and when you click on it a small popup window appears containing a text box. The desired effect is that the user can type in the text box and data will be filtered according to what was typed.
In my view model I already have my filter text box property that I want to use for binding:
public string PetNameFilterSearchBox
{
get
{
return _petNameFilterSearchBox;
}
set
{
_petNameFilterSearchBox = value;
RaisePropertyChanged(nameof(PetNameFilterSearchBox));
FilterData(); //As you're writing
}
}
private string _petNameFilterSearchBox = string.Empty;
public CollectionView PetDataFilterView { get; set; }
public bool OnFilterTriggered(object item)
{
if (item is AvailablePetInfo petInfo)
{
var pet_name = PetNameFilterSearchBox;
if (pet_name != string.Empty)
return (petInfo.DisplayName.Contains(pet_name));
}
return true;
}
public void FilterData()
{
CollectionViewSource.GetDefaultView(AvailablePetInfo).Refresh();
}
//Constructor
public PetInfoViewModel()
{
AvailablePetInfo = GetPetInfo();//gets the list
ContactFilterView = (CollectionView)CollectionViewSource.GetDefaultView(AvailablePetInfo);
ContactFilterView.Filter = OnFilterTriggered;
}
When I run my code I see the little button next to the header, I click on it and I see the textbox. But when I start typing I dont see my datagrid updating. I set some breakpoints in my PetNameFilterSearchBox and I find that when I start typing it's not getting hit. This tells me that there's something wrong with the binding. Can someone tell me what I'm doing wrong?
Your problem is one of DataContext.
I'll be assuming PetNameFilterSearchBox is a property of the Window hosting the DataGrid and that the appropriate DataContext is set at the Window level.
Normally, DataContext is inherited by child elements, so setting the DataContext for the Window would set it for all its children. But things change once you start using DataTemplates.
In a DataTemplate, the root DataContext is always the data object that's being displayed. In your case, that's the string "Pet Name". This is why you can put <ContentControl Content="{Binding}"/> inside the DataTemplate and have it display "Pet Name".
The downside is you can't put <TextBox Text="{Binding PetNameFilterSearchBox}"/> and expect it to bind to the Window, because that DataContext is being overridden by the DataTemplate.
Normally, you can get around the DataTemplate DataContext problem by using RelativeSource, which you can use walk up the visual tree and find another source to bind to. But this doesn't work inside a Popup because a Popup is not actually part of the Window's visual tree.
What will work is ElementName:
<TextBox Text="{Binding PetNameFilterSearchBox, Mode=TwoWay, ElementName=W}" Width="300"/>
In the above example, I set on my Window Name="W".

WPF: Disable items in bounded ItemsControl

I'm working on a WPF page with the following:
<ItemsControl ItemsSource="{Binding Peopl.PhoneNums}" x:Name="PhoneList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal" Margin="0,0,0,0" x:Name="PhoneEntry">
<TextBlock Text="123-456-78901"/>
<ComboBox ...>
</StackPanel>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
There can be multiple stackpanels, each with a unique phone number; in code behind, each phone number has a flag indicating if it should be enabled; I want to be able to enable each entry in the stack panel based on that flag but I'm stuck accessing it....
I have:
foreach (Phone phone in PhoneList.ItemsSource)
{
if (phone.ShouldBeDisabled)
{
int index = PhoneList.Items.IndexOf(phone);
PhoneList.IsEnabled = false;
//this disables the entire control;
// I can't access "PhoneEntry" here... hmm
}
}
Is there a way to disable only one entry at a time? How can I access PhoneEntry? Should I try to disable the each stackpanel entry based on the bound value?
You may invert your view model property and call it ShouldBeEnabled. Now you can bind the StackPanel's IsEnabled property.
<StackPanel ... IsEnabled="{Binding ShouldBeEnabled}">
...
</StackPanel>
In case you can't change the view model, you may use a binding converter that inverts the property value:
<StackPanel ... IsEnabled="{Binding ShouldBeDisabled,
Converter={StaticResource InverseBooleanConverter}}">
...
</StackPanel>
Your Phone class would have to implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the value of the ShouldBeDisabled property changes.

Setting Textblock/Button Visibility on WP8 App using MVVM

I'm new to MVVM and I'm working on a WP8 app and I'd like to be able to set the visibility of buttons and a textblock based on when one of those buttons were tapped. Here's my View to try and explain my problem a bit better; (http://i.imgur.com/JvrxBkh.png - can't post an image on this reputation) .
When the user taps the "Going to sleep" button, I'd like the counter textblock and the "I'm awake" button to be visible with the "Going to sleep" button to be collapsed. It'll then work the other way once the "I'm awake" button is pressed, etc. If I wasn't using MVVM I'd just set the Visibility value inside the button event, but I'm stuck on how to do this when using the MVVM pattern.
I've looked around and come across a solution using a converter such as using a BooleanToVisibilityConverter class and a bool property and then setting the visibility by binding to the bool value from the ViewModel and setting the converter value for the visibility to the StaticResource BooleanToVisibilityConverter. But it just doesn't work for me the way I want. Then my counter textblock has a bind already from the ViewModel so would I need some kind of multi-binding for this textblock?
Hopefully I've explained myself OK. It seems like it should be a simple task that maybe I'm just over thinking or something.
EDIT With some code snippets
The View components that I was referring to;
<BooleanToVisibilityConverter x:Key="boolToVis" />
<TextBlock
Grid.Row="2"
Text="{Binding Counter}"
FontSize="50"
TextWrapping="Wrap"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}"/>
<Button
Grid.Row="3"
Width="230"
Height="70"
Content="I'm awake"
BorderThickness="0"
Background="Gray"
Margin="0,20,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding AwakeButtonCommand}"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}""/>
<Button
Grid.Row="3"
Width="230"
Height="70"
Content="Going to sleep"
BorderThickness="0"
Background="Gray"
Margin="0,20,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Command="{Binding SleepButtonCommand}"
Visibility="{Binding VisibilityControl, Converter={StaticResource boolToVis}}"/>
Then in the ViewModel VisibilityControl is just;
private bool _visibilityControl;
public bool VisibilityControl
{
if (_visibilityControl != value)
_visibilityControl = value;
OnPropertyChanged("VisibilityControl");
}
And I have the two buttons such as (I'll just post one up);
public ICommand AwakeButtonCommand
{
get
{
return _awakeButtonCommand
?? (_awakeButtonCommand = new Resources.ActionCommand(() =>
{
VisibilityControl = true;
}));
}
}
It doesn't work, obviously. I think what's throwing me is because I want several things changed when one button is pressed, etc. It's throwing me off.
I've not done any Windows Phone development but here's one way of doing it in WPF that might be applicable to WP also.
First, your ViewModel would have a couple of Boolean properties indicating which state is active (one would be a mirror of the other):
public bool IsAwake
{
get
{
return _isAwake;
}
set
{
if (_isAwake != value)
{
_isAwake = value;
// raise PropertyChanged event for *both* IsAwake and IsAsleep
}
}
}
bool _isAwake;
public bool IsAsleep
{
get
{
return !_isAwake;
}
}
Then your View would contain both parts of the UI (asleep & awake) but would switch between the two parts by binding their Visibility property to these Boolean properties of your ViewModel:
<StackPanel>
<StackPanel x:Name="AwakePart"
Visibility="{Binding IsAwake, Converter={StaticResource btvc}}">
... "Going to sleep" button here ...
</StackPanel>
<StackPanel x:Name="AsleepPart"
Visibility="{Binding IsAsleep, Converter={StaticResource btvc}}">
... Elapsed time text block and "I'm awake" button here ...
</StackPanel>
</StackPanel>
You will also need a BooleanToVisibilityConverter instance somewhere in your XAML resources:
<... .Resources>
<BooleanToVisibilityConverter x:Key="btvc" />
</... .Resources>
I've used two Boolean properties in this example as it makes the XAML a little easier, however you could also use a DataTrigger -- assuming they have those in Windows Phone -- in which case you would only need one Boolean property. You would then write a trigger to toggle the Visibility properties of the two parts:
<DataTrigger Binding="{Binding IsAwake}" Value="True">
<Setter TargetName="AwakePart" Property="Visibility" Value="Visible" />
<Setter TargetName="AsleepPart" Property="Visibility" Value="Hidden" />
</DataTrigger>
For this to work you would need to explicitly set the "AwakePart" visibility to Hidden in the XAML to start with and ensure that in your ViewModel the IsAwake property is false by default. You would also need to remove the bindings on the visibility properties (as these would now be set via the trigger).

React on user clicking already selected ListViewItem, MVVM

I have a ListView that looks like this, that controls which tab in my application that is opened.
<ListView Grid.ColumnSpan="2" Grid.Row="1" SelectedItem="{Binding Path=SelectedSubstanceName}" Name="listView" ItemsSource="{Binding Path=Substances}" HorizontalAlignment="Stretch" Margin="2" VerticalAlignment="Stretch">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Lägg till" Command="{Binding AddSubstanceCommand}"/>
<MenuItem Header="Ta bort" Command="{Binding RemoveSubstanceCommand}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I use the SelectedSubstanceName property to detect which tab to open, or switch to, if it's already open.
The property looks like this:
private SubstanceName selectedSubstanceName;
public SubstanceName SelectedSubstanceName
{
get
{
return selectedSubstanceName;
}
set
{
selectedSubstanceName = value;
OnPropertyChanged("SelectedSubstanceName");
if (selectedSubstanceName != null)
{
if (!Tabs.Any(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel))))
AddTab(selectedSubstanceName);
else
SelectedTab = Tabs.First(t => t.Identify(selectedSubstanceName.SubstanceNameID, typeof(SubstanceTabsViewModel)));
}
}
}
The case I'm not able to cover is when the user clicks "someSubstance", the corresponding tab is opened, the user closes it, and "someSubstance" is still selected. If the user wants to open it again, he has to select some other substance (which will then be opened), and then click "someSubstance" again. Is it possible to trigger the property even when clicking the same ListViewItem?
I know I could add an event on double-click, but ideally, I want to avoid both events and double-clicks.
I think the problem is that after clicking an item the first time the list's SelectedItem gets set. After clicking the same item the second time SelectedItem won't change because it is already set to that item. What you should do is set the SelectedItem to null after handling the click.
Try to unselect all Items in your ListView after the tab is closed.
YOURLISTVIEW.UnselectAll();
So the next time someone selects an Item there will be a change.
You don't actually want to use the ListView class, but instead simply use the ItemsControl, since it is the most basic way of representing a sequence of elements, but without the extras such as SelectedItem, SelectedValue, etc. that any class deriving from Selector has.
From there, it's merely a matter of how to represent each item in the ItemsControl. The behavior you want is to know when a specific item has been clicked on, which would make the Button class a good candidate, since it handles click behavior through an ICommand interface. Obviously, since you know about DataTemplates and styling in general, you should already know that you can customize how the button looks (visually) without sacrificing the actual behavior (click-handling).
<ItemsControl ItemsSource="{Binding Path=Substances}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource SomeStyleToChangeItsLook}"
Command="{Binding Path=SelectSubstanceCommand}"
CommandParameter="{Binding}"
Content="{Binding Path=Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public ICommand SelectSubstanceCommand { get; private set; }
private void SelectSubstance(object parameter)
{
// Add the substance that was "clicked" on here however you want to do it.
}
Keep in mind I don't know what framework you are using, so I just gave a general example of how the Command code might look in your view-model. The key to MVVM and using WPFs awesome UI is to always think of what behavior you want and which controls offer that behavior. Ignore how they actually look because that can be changed without losing that behavior.

WPF ToolTip does not update

Assuming I have a simple class that represents a staff member
class Staff
{
public string FirstName { get; set; }
public string FamilyName { get; set; }
public int SecondsAlive { get; set; }
}
and I have a DataTemplate for staff
<DataTemplate DataType={x:Type Staff}>
<StackPanel Orientation="Horizontal">
<TextBlock Text={Binding FirstName}/>
<TextBlock Text=" ">
<TextBlock Text={Binding FamilyName}/>
<StackPanel.ToolTip>
<TextBlock Text={Binding SecondsAlive}/>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
I then show a whole bunch of staff in a ListBox
myListBox.ItemsSource = GetAllStaff();
Pretty standard stuff. The problem I have is that the tooltip which shows the number of seconds that someone has been alive does not get updated. When you first mouse over a staff member then it works fine but from then on it keeps that value for ever. I could implement INotifyPropertyChanged to get around this but it seems like overkill to do this for every staff member whenever SecondsAlive changes. Say I have 400 staff in the list then I have to raise 400 events even though the user might never look at another tooltip. What I would like is to make the tooltip request the SecondsAlive property ever time it is shown. Is that possible?
Please note that this is just an example and I don't need to know how many seconds my staff have been alive :-) But I have the same issue that I need to raise an even around 400 times just for a tooltip which someone probably won't look at.
OMG!!! I have finally found the solution to this problem!!! This has been bugging me for months. I'm not surprised no one answered this because the code I typed out at the top actually DIDN'T show the problem I was trying to reproduce, in fact it showed the solution. The answer is that if you define your tooltip like this
<StackPanel.ToolTip>
<TextBlock Text="{Binding SecondsAlive}"/>
</StackPanel.ToolTip>
Then everything works just fine and dandy and there is no need to raise a propertyChanged event on "SecondsAlive". The framework will call the SecondsAlive property every time the tooltip is shown. The problem comes when you define your tooltip like this:
<StackPanel.ToolTip>
<ToolTip>
<TextBlock Text="{Binding SecondsAlive}"/>
</ToolTip>
</StackPanel.ToolTip>
Having the extra tooltip tag in there makes sense, surely you need to create a tooltip object to assign it to the tooltip property but this is incorrect. What you are assigning to the tooltip property is actually the content of the tooltip. I was assuming you needed to give it controls such as textblock and image to display but you can pass in anything and it will display the content just like a content control. Seeing it inherits from content control this makes sense :-) It all seems obvious once you know :-)
Thanks everyone for looking at this.
PS. I found an additional problem in that the next logical step in simplifying code is to just assign text straight to the tooltip like this (assuming your tooltip is plain text):
<TextBlock Text="{Binding Path=StaffName}" ToolTip="{Binding Path=StaffToolTip}"/>
This also causes the original problem I was having. This makes sense because the results of the property StaffToolTip get assigned to the tooltip property and never get called again. However, it doesn't quite make sense why then assigning a TextBlock to the tooltip property actually solves the problem.
Although this is an old question.
In this case:
<StackPanel.ToolTip>
<ToolTip>
<TextBlock Text="{Binding SecondsAlive}"/>
</ToolTip>
</StackPanel.ToolTip>
The ToolTip control is hosted by an isolate HWND (a.k.a, a native window). It should have its own DataContext, the correct binding expresion should be like:
<StackPanel.ToolTip>
<ToolTip
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<TextBlock Text="{Binding SecondsAlive}"/>
</ToolTip>
</StackPanel.ToolTip>
In this particular case there is a cool trick you can use
Seconds Alive Now = Seconds Alive originally + Elapsed Time
You can bind to the Elapsed Time property and specify a converter that adds the initial value to it. That way you only need to raise 1 event and the tooltips would all be updated.
Edit: You can add the ElapsedTime property (with INotifyPropertyChanged) to many places -- one logical place could be to the collection that is storing your Staff objects
Edit: You would also need to bind each tooltip to the shared ElapsedTime property rather than the SecondsAlive property
It's worth noting that the ToolTip appears to check your object that it's bound to for equality before reloading itself with the new data.
In my case I did an override of
public override bool Equals(object obj)
and
public override int GetHashCode()
on a class with properties
public class MultipleNameObject { string Name, string[] OtherNames};
Unfortunatley I only did a string.Compare() on the Name property of the MultipleNameObject for equality purposes. The tool tip was supposed to display OtherNames in a ItemsControl, but was not updating if Name was equal on the previous MultipleNameObject that the mouse hovered been over on the grid, even if the OtherNames were different.
[edit] Running with debug enabled confirms that the GetHashCode() override of my object was being used by the ToolTip to decide whether to grab the new data back. Fixing that to take the string[] OtherNames into account fixed the problem.
This example shows how to add a tooltip to a grid that recalculates the tooltip on demand, when the user hovers over a cell in the grid.
This is useful if you have a huge grid with 10,000 items, and you want to update the grid continuously - but you don't want to update the tooltips continuously, as this is time consuming.
This example is for Infragistics, but the principle applies equally to other fine libraries such as DevExpress.
Xaml
<ig:TemplateColumn Key="ColumnKey">
<ig:TemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="Header"></TextBlock>
</DataTemplate>
</ig:TemplateColumn.HeaderTemplate>
<ig:TemplateColumn.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel.ToolTip>
<TextBlock Text="{Binding ToolTip}"/>
</StackPanel.ToolTip>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter" >
<i:InvokeCommandAction Command="{Binding ToolTipHoverRefreshCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</StackPanel>
</DataTemplate>
</ig:TemplateColumn.ItemTemplate>
</ig:TemplateColumn>
ViewModel
public ICommand ToolTipHoverRefreshCommand => new DelegateCommand(() =>
{
this.OnPropertyChanged(this.ToolTip);
});
public string ToolTip
{
get
{
return DateTime.Now.ToString();
}
set
{
this.OnPropertyChanged(nameof(this.ToolTip));
}
}
I was having the same problem of it not updating. I found the solution to be adding the controls to a ToolTip template:
<DataTemplate DataType={x:Type Staff}>
<StackPanel Orientation="Horizontal">
<TextBlock Text={Binding FirstName}/>
<TextBlock Text=" ">
<TextBlock Text={Binding FamilyName}/>
<StackPanel.ToolTip>
<Tooltip>
<ToolTip.Template>
<ControlTemlate>
<TextBlock Text={Binding SecondsAlive}/>
</ControlTemplate>
</ToolTip.Template>
</Tooltip>
</StackPanel.ToolTip>
</StackPanel>
</DataTemplate>
I don't quite understand why this is needed or why this makes it behave differently but it fixed the problem.

Categories