MasterDetail ListView and editable ContentPresenter: what is wrong? - xaml

I'm based on the official Microsoft sample to create a MasterDetail ListView:
MasterDetail ListView UWP sample
I have adapted it to my case, as I want that users can edit directly selected items from the ListView. But I meet a strange comportement:
when I add a new item to the ListView, the changes of the current item, done in the details container, are well saved
but when I select an existing item in the ListView, the changes of the current item, done in the details container, are not saved
Here is a screenshot of my app:
The XAML of my ListView is like this:
<!-- Master : List of Feedbacks -->
<ListView
x:Name="MasterListViewFeedbacks"
Grid.Row="1"
ItemContainerTransitions="{x:Null}"
ItemTemplate="{StaticResource MasterListViewFeedbacksItemTemplate}"
IsItemClickEnabled="True"
ItemsSource="{Binding CarForm.feedback_comments}"
SelectedItem="{Binding SelectedFeedback, Mode=TwoWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.FooterTemplate>
<DataTemplate>
<CommandBar Background="White">
<CommandBar.Content>
<StackPanel Orientation="Horizontal">
<AppBarButton Icon="Add" Label="Add Feedback"
Command="{Binding AddItemFeedbacksCommand}" />
<AppBarButton Icon="Delete" Label="Delete Feedback"
Command="{Binding RemoveItemFeedbacksCommand}" />
</StackPanel>
</CommandBar.Content>
</CommandBar>
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
The XAML of the ListView's ItemTemplate is:
<DataTemplate x:Key="MasterListViewFeedbacksItemTemplate" x:DataType="models:Feedback_Comments">
<StackPanel Margin="0,11,0,13"
Orientation="Horizontal">
<TextBlock Text="{x:Bind creator }"
Style="{ThemeResource BaseTextBlockStyle}" />
<TextBlock Text=" - " />
<TextBlock Text="{x:Bind comment_date }"
Margin="12,1,0,0" />
</StackPanel>
</DataTemplate>
The XAML of the Details container is like this:
<!-- Detail : Selected Feedback -->
<ContentPresenter
x:Name="DetailFeedbackContentPresenter"
Grid.Column="1"
Grid.RowSpan="2"
BorderThickness="1,0,0,0"
Padding="24,0"
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
Content="{x:Bind MasterListViewFeedbacks.SelectedItem, Mode=OneWay}">
<ContentPresenter.ContentTemplate>
<DataTemplate x:DataType="models:Feedback_Comments">
<StackPanel Visibility="{Binding FeedbacksCnt, Converter={StaticResource CountToVisibilityConverter}}">
<TextBox Text="{Binding creator, Mode=TwoWay}" />
<DatePicker Date="{Binding comment_date, Converter={StaticResource DateTimeToDateTimeOffsetConverter}, Mode=TwoWay}"/>
<TextBox TextWrapping="Wrap" AcceptsReturn="True" IsSpellCheckEnabled="True"
Text="{Binding comment, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
<ContentPresenter.ContentTransitions>
<!-- Empty by default. See MasterListView_ItemClick -->
<TransitionCollection />
</ContentPresenter.ContentTransitions>
</ContentPresenter>
The "CarForm" is the main object of my ViewModel. Each CarForm contains a List of "Feedback_Comments".
So in my ViewModel, I do this when I add a new comment:
private void AddItemFeedbacks()
{
FeedbacksCnt++;
CarForm.feedback_comments.Add(new Feedback_Comments()
{
sequence = FeedbacksCnt,
creator_id = user_id,
_creator = username,
comment_date = DateTime.Now
});
SelectedFeedback = CarForm.feedback_comments[CarForm.feedback_comments.Count - 1];
}
=> the changes done in the Feedback_Comment that was edited before the add are well preserved
I don't do anything when the user select an existing Feedback_Comment: this is managed by the XAML directly.
=> the changes done in the Feedback_Comment that was edited before to select anoter one are not preserved
=> Would you have any explanation?

The TwoWay binding for the Text property is updated only when the TextBox loses focus. However, when you select a different item in the list, the contents of the TextBox are no longer bound to the original item and so are not updated.
To trigger the update each time the Text contents change, so that the changes are reflected immediately, set the UpdateSourceTrigger set to PropertyChanged:
<TextBox Text="{Binding comment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Triggering changes everywhere
To ensure your changes are relflected everywhere including the list, you will need to do two things.
First, your feedback_comments is of type ObservableCollection<Feedback_Comments>. This ensures that the added and removed items are added and removed from the ListView.
Second, the Feedback_Comments class must implement the INotifyPropertyChanged interface. This interface is required to let the user interface know about changes in the data-bound object properties.
Implementing this interface is fairly straightforward and is described for example on MSDN.
The quick solution looks like this:
public class Feedback_Comments : INotifyPropertyChanged
{
// your code
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [ CallerMemberName ]string propertyName = "" )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
Now from each of your property setters call OnPropertyChanged(); after setting the value:
private string _comment = "";
public string Comment
{
get
{
return _comment;
}
set
{
_comment = value;
OnPropertyChanged();
}
}
Note, that the [CallerMemberName] attribute tells the compiler to replace the parameter by the name of the caller - in this case the name of the property, which is exactly what you need.
Also note, that you can't use simple auto-properties in this case (because you need to call the OnPropertyChanged method.
Bonus
Finally as a small recommendation, I see you are using C++-like naming conventions, which does not fit too well into the C# world. Take a look at the recommended C# naming conventions to improve the code readability :-) .

Related

Binding with Prism Navigation

I'm trying to bind string path in XAML using prism navigateto extension method from a button in ListView.
Apparently the BindingContext isn't recognized to be the same as in the ListView
Here's the sample code of what i'm trying to achieve.
<ListView
x:Name="MainMenu"
CachingStrategy="RetainElement"
HasUnevenRows="True"
ItemsSource="{Binding MenuItems}"
Margin="20,0,0,0"
SeparatorVisibility="Default">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<buttons:SfButton
Style="{StaticResource BaseButtonStyle}"
HeightRequest="70"
TextColor="{StaticResource MenuTextColor}"
Text="{Binding Title}"
HorizontalTextAlignment="Start"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
ImageSource="{Binding MenuItemType, Converter={StaticResource MenuItemTypeConverter}}"
ShowIcon="True"
Command="{prism:NavigateTo Name={Binding Item.View}}"
/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Any ideas how to set the binding in this situations?
Regards.
Within any sort of ListView, CollectionView, etc where you are binding an ItemsSource and then have some sort of DataTemplate to display an individual item in that collection, the Binding Context within that DataTemplate is the individual item in the collection not the ViewModel that that provides the Binding Context of both your Page and the ListView.
There are technically a couple parts to this that you will want to understand. Let's say that your model looks like:
public class FooModel
{
public string Text { get; set; }
public string NavigationPath { get; set; }
}
And let's say that you have a collection of FooModel like:
public ObservableCollection<FooModel> FooItems { get; }
In XAML you might have something like:
<ListView ItemsSource="{Binding FooItems}">
However when you go to reference properties of FooItem you will just reference them like:
<ListView.DataTemplate>
<DataTemplate>
<ViewCell>
<Button Text="{Binding Text}"
Command="{prism:NavigateTo Name={Binding NavigationPath}" />
</ViewCell>
</DataTemplate>
</ListView.DataTemplate>
Now assuming that the issue isn't that you're just adding Item erroneously, let's look at some other possible issues/solutions. To start let's look at the start of our page.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
x:Name="page"
x:Class="HelloWorld.Views.ViewA">
The big thing to notice here is that I've added the x:Name attribute so that I can reference the page itself later. In general within the context of something like a ListView if I need to access say the FooCommand property within my ViewModel I might would change my XAML markup from:
<Button Command="{Binding FooCommand}" />
To instead look at the Page's BindingContext like this:
<Button Command="{Binding BindingContext.FooCommand, Source={x:Reference page}}" />
While this will help you in general within your ListView it still doesn't necessarily help you with the issue of using Prism's Navigation Extensions. For this you may need to pass in the SourcePage like the following:
<Button Command="{prism:NavigateTo 'Foo', SourcePage={x:Reference page}}" />
In the event this doesn't work for some reason then you may be possible that the BindingContext isn't getting set properly on the Navigation Extension itself. To work around this you would want to update your command as follows:
<Button x:Name="cellButton"
Command="{prism:NavigateTo Name={Binding View},
BindingContext={x:Reference cellButton}}" />
Note that if you need to reference the SourcePage or add the BindingContext to resolve your issue, please provide a sample that reproduces the issue and open an issue on GitHub.

Updating ListView on property change

I am having a strange problem in my UWP application.
I have a ListView bound to an ObservableCollection<SomeInterface>. I am changing one property of all the items in the ObservableCollection. The problem is that because of the ObservableCollection does not take a class as the type argument, rather it takes an interface, I cannot figure out how to implement INotifyCollectionChanged on the interface.
So I went ahead and changed the property of all items in the collection ( without implementing INotifyPropertyChanged ). This is giving weird behavior. When I change the property ( I do this as soon as I navigate to the page containing the ListView ) It only affects only those ListView items that are not in view currently ie, if there are 100 items in the ListView and I can see the first 10 without scrolling down then those 10 items are unchanged but when I scroll down, I see that other (other than the first 10) ListViewItems are reflecting the changes that were made. And to add to this, when I scroll up again ( to the first 10 items ), I see that now they are also changed.
to summarize, only the items that are not currently in the view get updated.
here's my code to update the ObservableCollection:
class SomePage : Page
{
private ObservabeCollection<SomeInterface> SomeObservableCollection { get; set; } = new ObservabeCollection<SomeInterface>();
...
private async Task ModifyObservableCollection()
{
var response = await MakeApiCall();
var facets = response.ListOfItems;
foreach (var item in SomeObservableCollection.ToList())
{
var fromApi = facets.author.FirstOrDefault(i => i.key == item.key);
if (fromApi == null) continue;
var itemInList = SomeList.FirstOrDefault(i => i.key == fromApi.key);
itemInList.read = fromApi.read;
itemInList.num = fromApi.num;
//here!!
itemInList = SomeObservableCollection.FirstOrDefault(i => i.key == fromApi.key);
itemInList.read = fromApi.read;
itemInList.num = fromApi.num;
}
}
}
This is my ListView:
<ListView ItemsSource="{x:Bind AuthorFacets, Mode=OneWay}"
ItemTemplate="{StaticResource ListViewDataTemplate}"
SelectionMode="Multiple">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
My itemtemplate:
<DataTemplate x:Key="ListViewDataTemplate"
x:DataType="local:ISomeInterface">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind read}"/>
<TextBlock Text="{x:Bind num}"/>
</Grid>
</DataTemplate>
Change
<TextBlock Text="{x:Bind read}"/>
<TextBlock Text="{x:Bind num}"/>
to
<TextBlock Text="{x:Bind read, Mode=OneWay}"/>
<TextBlock Text="{x:Bind num, Mode=OneWay}"/>
And implement INotifyPropertyChanged, and it should work. By default, x:Bind is OneTime unlike traditional binding (because performance / memory) so your items in view won't update.
Without INPC? You could possibly try calling Bindings.Update() on the parent view after changing the properties, though you may lose scroll position.

how to put images on tabs (pivots) in windows 10 app

I have read most of the Windows 10 UI design guidelines and here are some pictures of examples of a pivot navigation that is essentially a tab navigation with images: https://msdn.microsoft.com/en-us/library/windows/apps/dn997788.aspx#examples
I was unable to find out how to put images on these tabes (pivotitems).
<Pivot x:Name="mainTabs">
<PivotItem x:Name="Header1" Header="Header1" Background="{x:Null}" Foreground="{x:Null}"/>
<PivotItem x:Name="Header2" Header="Header2"></PivotItem>
<PivotItem x:Name="Header3" Header="Header3"></PivotItem>
<PivotItem x:Name="Header4" Header="Header4"></PivotItem>
<PivotItem x:Name="Header5" Header="Header5"></PivotItem>
</Pivot>
HeaderTemplate works OK for replacing text with pictures but then text is missing, and I would like to keep the text like shown in Windows 10 UI guidelines.
<Pivot x:Name="mainTabs">
<Pivot.HeaderTemplate>
<DataTemplate>
<Image Source="{Binding}"></Image>
</DataTemplate>
</Pivot.HeaderTemplate>
<PivotItem x:Name="Header1" Header="Assets/play1.png"></PivotItem>
<PivotItem x:Name="Header2" Header="Assets/play2.png"></PivotItem>
</Pivot>
Nokia Developer website had some really great article about how to make a tabbed pivot headers like official Instagram or Twitter app in Windows Phone.However , that article is not available for now because Microsoft decided to ignore all of Nokia Developer content , unfortunately.
I researched a bit and found this article instead
http://depblog.weblogs.us/2013/08/29/twitterate-your-windows-phone-app/
PivotItem headers' are being used in PivotHeaderTemplate AFAIR.Basically you can follow the article above or just change your Pivot's HeaderTemplate.
Write a converter that converts your Header property to Text and returns it.
public class ImageToTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var sent = value as string;
switch(sent)
{
case "Assets/play1.png":
return "Play 1 Header";
case "Assets/play2.png":
return "Play 2 Header";
default:
return string.Empty;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Add this converter's namespace to your XAML and define it in your Resources.Then change your template as
<DataTemplate>
<StackPanel>
<Image Source="{Binding}"></Image>
<TextBlock Text="{Binding Source='',Converter={StaticResource ImageToTextConverter}}" />
</StackPanel>
</DataTemplate>
I know this post is old and no one likes to kick a dead horse, but I figured I might share my solution that doesn't rely on a value converter and allows you to simply set an image with text. Just for anyone who is looking to add images or anything to a pivot item since there does not seam to be a simple solution out there.
Add a stack panel to the pivot item header with image and text. That's it.
<Pivot x:Name="Tabs" Grid.Row="1" Grid.Column="0" >
<PivotItem>
<PivotItem.Header>
<StackPanel VerticalAlignment="Top">
<Image Width="25" Height="25" Source="../Assets/Home.png"/>
<TextBlock Text="Home Tab"/>
</StackPanel>
</PivotItem.Header>
<Grid>
<TextBlock Text="asdfasfasdfasdfasdf"/>
</Grid>
</PivotItem>
<PivotItem>
<PivotItem.Header>
<StackPanel VerticalAlignment="Top">
<Image Width="25" Height="25" Source="../Assets/Schedule.png"/>
<TextBlock Text="Home Tab"/>
</StackPanel>
</PivotItem.Header>
<Grid>
<TextBlock Text="134123475"/>
</Grid>
</PivotItem>
</Pivot>

Binding an ExpanderView do a viewModel?

I made some ExpanderViews and hardcoded everything. That worked and looked nice so I wanted to clean up and only write one ExpanderView in xaml and load everything else with a binding.
As far as I understood I need a ListBox around the whole thing to make it more dynamic?
This is my code so far:
<ListBox ItemsSource="{Binding ContactDe}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding LocationName}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocationName}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Information}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ContactViewModel-Class:
public class ContactDeViewModel : INotifyPropertyChanged
{
private string _locationName;
public string LocationName
{
get
{
return _locationName;
}
set
{
if (value != _locationName)
{
_locationName = value;
NotifyPropertyChanged("LocationName");
}
}
}
private List<string> _information;
public List<string> Information
{
get
{
return _information;
}
set
{
if (value != _information)
{
_information = value;
NotifyPropertyChanged("Information");
}
}
}
}
And this is where I fill the ContactViewModel:
this.ContactDe.Add(new ContactDeViewModel()
{
LocationName = "Stuttgart",
Information = new List<string>
{
"some text"
}
}
);
this.ContactDe.Add(new ContactDeViewModel()
{
LocationName = "Böblingen",
Information = new List<string>
{
"more text"
}
}
);
I made a SampleViewModel-File where I have:
<vm:MainViewModel.ContactDe>
<vm:ContactDeViewModel LocationName="Location 1" />
<vm:ContactDeViewModel LocationName="Location 2" />
</vm:MainViewModel.ContactDe>
In the preview-window it shows me 2 ExpanderViews with Location 1 and 2. But the same code doesn't work with the emulator or a real device. I don't really understand which Binding-Acces does what. It would already help me a lot if I could see a full example. I googled many tutorials but most only show 1 side, like a xaml without seing how the data is stored.
edit:
Now I edited the viewModel, so it's not a List<string> but a List<Info> with Info only containing string Text. So now I can say ItemsSource="{Binding Text}" which should be only 1 string at a time, right?
As stated in comment to #dellywheel's answer, that you set DataContext this way :
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
that set DataContext for use in design-time only, hence it doesn't work in run-time. To set DataContext with similar approach for use in run-time, you can try this way :
<ListBox ItemsSource="{Binding ContactDe}">
<ListBox.DataContext>
<vm:MainViewModel/>
</ListBox.DataContext>
........
........
</ListBox>
or this way to set DataContext in page level :
<phone:PhoneApplicationPage>
<phone:PhoneApplicationPage.DataContext>
<vm:MainViewModel/>
</phone:PhoneApplicationPage.DataContext>
........
........
</phone:PhoneApplicationPage>
Another suggestion, prefer ObservableCollection rather than List for use along with data binding. ObservableCollection automatically notify view to refresh whenever item added to or removed from collection.
You need to change your bindings slightly
<toolkit:ExpanderView Header="{Binding LocationName}"
ItemsSource="{Binding Information}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{StaticResource honeFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
Hope that helps

XAML how to make a textblock/text box edit toggle?

I may be approaching this all wrong, so tell me if you have an alternative suggestions.
I'm making an app for windows RT that will have a bunch of text blocks displayed to the user, for example character stats.
The user will see:
Str: 10
Con: 10
Dex: 10
and so on.
I want them to be able to fill these in, then have a select view values calculated based on the result.
My though was to click an "Edit" button at the top and toggle some text boxes over each editable text block.
When trying to set this up using "Blend for Visual Studio" I can't seem to make a text box that is smaller than 49x34 (much larger than my text blocks).
I was going to find a way to generate a text box for each text block (using its dimensions) on button click, but since they will always be the same and there will be a lot of them I was trying to make them static via blend.
I'm pretty new to XAML, and I can't seem to find a good example of people setting up editable fields like this, so how should I make a bunch of static fields have editable text boxes?
I would create both the TextBox and TextBlock overlays in XAML, and place them directly on top of each other in a Grid, using Horizontal and Vertical alignments to "Center" to ensure that the text is always completely lined up. I would also use static Widths to ensure that the columns line up well.
From there, you can directly bind the Visibility to some boolean "IsEditing" property, to make sure that only one of the controls are shown at a time.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Str: " Width="40" VerticalAlignment="Center" />
<Grid Width="40" VerticalAlignment="Center">
<TextBlock Text="{Binding Strength}"
Visibility="{Binding IsEditing, Converter={StaticResource BooleanToInvisibilityConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<TextBox Text="{Binding Strength}"
Visibility="{Binding IsEditing, Converter={StaticResource BooleanToVisibilityConverter}}"
VerticalAlignment="Center"
HorizontalContentAlignment="Center" />
</Grid>
</StackPanel>
Somewhere along the way you'll have to define your "BooleanToVisibility" and "BooleanToInvisiblity" converter resources. I like this implementation by Diedrik Krols. It's nice and simple, with the option to invert.
You might want to use a style for a TextBox, which changes depending on whether or not the "IsReadOnly" property is true or not.
When IsReadOnly is true, you can set the BorderBrush and Background to Transparent, thus making it look like a normal textblock.
In this way, you don't have to overlay TextBlocks and TextBoxes; just use TextBox controls by themselves, and toggle the "IsReadOnly" property when you click the Edit button.
In your resources:
<Style x:Key="MyEditableField" TargetType={x:Type TextBox}>
<Style.Triggers>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Background" Value="Transparent" />
</DataTrigger>
</Style.Triggers>
</Style>
And here's one of your editable fields:
<StackPanel Orientation="Horizontal">
<TextBlock Text="Str: " />
<TextBox Style="{StaticResource MyEditableField}"
Text="{Binding Strength}"
IsReadOnly="{Binding IsEditingDisabled}" />
</StackPanel>
Late answer, but who wants can also create a custom editable textbox, its pretty easy actually here is the code (obviously you can modify it for your own needs)
public class EditableTextBox : TextBox
{
public EditableTextBox()
{
this.BorderBrush = new SolidColorBrush(Colors.Black);
}
protected override void OnTapped(TappedRoutedEventArgs e)
{
this.IsReadOnly = false;
SetEditingStyle();
base.OnTapped(e);
}
protected override void OnDoubleTapped(DoubleTappedRoutedEventArgs e)
{
this.IsReadOnly = false;
SetEditingStyle();
base.OnDoubleTapped(e);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
this.IsReadOnly = true;
SetReadonlyStyle();
base.OnLostFocus(e);
}
public void SetReadonlyStyle()
{
this.BorderBrush.Opacity = 0;
this.Background.Opacity = 0;
}
public void SetEditingStyle()
{
this.BorderBrush.Opacity = 1;
this.Background.Opacity = 1;
}
}
Sample:
Tutorial: Full tutorial url
Using a property to toggle edit mode between view AND viewmodel is a bad design approach you should use events and command binding to communicate changes of states like this between view and viewmodel.
Here is an article that describes the principle in an MVVM compliant way:
http://www.codeproject.com/Articles/802385/A-WPF-MVVM-In-Place-Edit-TextBox-Control
Please have a look and tell me what you think.
This builds off of BTownTKD's solution, but as I really do prefer as much WPF of a solution as possible here is a bit of a modification, in my case I'm trying to modify the name of a tab control.
My view model has the following code:
private bool _isEditingName = false;
public bool IsEditingName
{
get
{
return _isEditingName;
}
set
{
_isEditingName = value;
OnPropertyChanged();
}
}
public ICommand StartEditing
{
get
{
return new DelegateCommand(() =>
{
IsEditingName = true;
});
}
}
public ICommand EndEditing
{
get
{
return new DelegateCommand(() =>
{
IsEditingName = false;
});
}
}
Next is my view that has the data template for the tab (not the content just the tab):
<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid VerticalAlignment="Center">
<TextBlock x:Name="TabName" Text="{Binding Name}" Visibility="{Binding IsEditingName, Converter={StaticResource InvertedBoolToVisConverter}}" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextAlignment="Left">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding StartEditing}" />
</TextBlock.InputBindings>
</TextBlock>
<TextBox Text="{Binding Name}" Visibility="{Binding IsEditingName, Converter={StaticResource BoolToVisConverter}}" VerticalAlignment="Center" HorizontalContentAlignment="Stretch" TextAlignment="Left" IsVisibleChanged="TextBox_IsVisibleChanged">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding EndEditing}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding EndEditing}" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
And last but not least, I wanted a double click to put me in edit mode, and to auto focus on the textbox and select all of the content for immediate typing. None of the xaml solutions were as clean as a simple code behind so I finally just decided on adding this to the textbox on visibility changed handler:
private void TextBox_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
var box = sender as TextBox;
if (box != null)
{
if ((bool)e.NewValue)
{
box.Focus();
box.SelectAll();
}
}
}
Out of all of the solutions I found, this was by far my favorite. Thanks everyone for your posts!! Helped me find a really good overall solution to my problem!