Contents of Content Control gone on tab switch - wpf

I am using mvvm. I am loading a usercontrol that contains a content control on two different tabs like so:
<TabControl>
<TabItem Header="View">
<StackPanel>
<Info:UserData/><!--UserData Control-->
<Button Content="View Entries" Command="{Binding BeginView}"/>
</StackPanel>
</TabItem >
<TabItem Header="Edit">
<StackPanel>
<Info:UserData/><!--UserData Control-->
<Button Content="Edit Entries" Command="{Binding BeginEdit}"/>
</StackPanel>
</TabItem >
</TabControl>
The User Control looks like:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding UserTypeInfo}"/>
<Info:UserDetailsArea Grid.Row="1"/>
</Grid>
When the tab first loads the content of the ContentControl is set to an image. Depending on some actions the content may change to a datatable, video, etc. This part works fine.
When it loads the default tab is the first one. If I click on the second tab, you should see the same thing - with a different button, this works. But if I go back to the first tab the Content control is empty.
What do I need to do so that both tabs display the image?
The value bound to from the viewmodel as requested:
private object userTypeInfo
/// <summary>
/// User Specific data
/// </summary>
public object UserTypeInfo
{
get
{
return userTypeInfo;
}
private set
{
UuserTypeInfo= value;
OnPropertyChanged("UserTypeInfo");
}
}
Edit:
The following is a simplified example that I believe shows the same problem:
XAML code for window:
<Window x:Class="dualCC.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">
<Grid>
<TabControl>
<TabItem Header="One">
<StackPanel>
<Button Content="One" />
<ContentControl Name="CCone"/>
</StackPanel>
</TabItem>
<TabItem Header="Two">
<StackPanel>
<Button Content="Two" />
<ContentControl Name="CCtwo"/>
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>
Code behind (you'll need to fix the path to an image):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace dualCC
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Uri uri = new Uri(#"C:\Image.jpg");
BitmapImage temp = new BitmapImage(uri);
Image CurrentImage = new Image();
CurrentImage.Source = temp;
CCone.Content = CurrentImage;
CCtwo.Content = CurrentImage;
}
}
}

This isn't MVVM. In MVVM you never manipulate GUI elements directly in code-behind like this.
To answer your question, the problem is that you're creating an Image, which is actually a child control, and setting it as the content of two separate controls. Controls can only have one parent. Create separate Images instead and set the BitmapImage as the source for each:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Uri uri = new Uri(#"C:\Image.jpg");
BitmapImage temp = new BitmapImage(uri);
CCone.Content = new Image { Source = temp };
CCtwo.Content = new Image { Source = temp };
}
Or better yet use proper MVVM and do it with data binding.

Related

Remove an attribute from xaml using c# - WPF

This question is related to removing an attribute in xaml.
In the below code, I have a Span. During an event, I add a back ground to this Span. During another event, I need to remove it. Please let me know, if there is a way to remove the background attribute I set to Span.
My Xaml looks like this.
<Window x:Class="WpfApplication4.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:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<RichTextBox x:Name="rtb">
<FlowDocument>
<Paragraph>
<Span x:Name="def" Tag="default">
<Run x:Name="deg">Some Text</Run>
</Span>
</Paragraph>
</FlowDocument>
</RichTextBox>
<TextBox x:Name="tx" Grid.Row="1" TextWrapping="Wrap"/>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="s" Click="Button_Click"/>
<Button Content="bg-Add" Grid.Column="1" Click="Button_Click"/>
<Button Content="bg-Remove" Grid.Column="2" Click="Button_Click"/>
</Grid>
</Grid>
</Window>
My code looks like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows. Shapes;
namespace WpfApplication4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if ((sender as Button).Content.ToString() == "s")
{
tx.Text = XamlWriter.Save(def);
}
else if ((sender as Button).Content.ToString() == "bg-Add")
{
def.Background = new SolidColorBrush(Colors.Blue);
}
else if ((sender as Button).Content.ToString() == "bg-Remove")
{
//Need to remove the set back color so that I get the default back
}
}
}
}
I am able to find an answer myself.
def.ClearValue(Span.BackgroundProperty);
is going to remove that attribute for me.
You can override ShouldSerializeProperty
public class CustomizedSpan : Span
{
public bool IsRemoveBackGround;
protected override bool ShouldSerializeProperty(DependencyProperty dp)
{
if (dp == Span.BackgroundProperty && IsRemoveBackGround)
{
return false;
}
else
{
return base.ShouldSerializeProperty(dp);
}
}
}

Specify column added order in a WPF Grid in code behind?

Adding a column to a grid in code behind is easy:
col10 = new ColumnDefinition();
col10.SharedSizeGroup = "column1";
When you add the column it adds to the end of the grid for example you have a grid with columns A and B, you use the code above and a new column (C) and it is added as A B C.
Is it possible to set it up like this?
C A B
Instead on adding to the end its added to the front?
Thanks
ColumnDefinitions are like any other Collection and support the IList<> interface.
So just use an insert method to control added order.
ColumnDefinition myColumn = new ColumnDefintion();
Grid myGrid = new Grid();
myGrid.ColumnDefinitions.Insert(0, myColumn);
Try this:
XAML file:
<Window x:Class="DataGridAddColumn.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>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ComboBox Name="cbWhere" Width="100" VerticalAlignment="Center">
<ComboBoxItem>Front</ComboBoxItem>
<ComboBoxItem>End</ComboBoxItem>
</ComboBox>
<TextBlock Text="Name:" VerticalAlignment="Center" Margin="10,0,0,0" />
<TextBox Name="tbName" MinWidth="100" VerticalAlignment="Center" />
<Button Content="Create" VerticalAlignment="Center" Margin="10,0,0,0" Click="Button_Click" />
</StackPanel>
<DataGrid Grid.Row="1" Name="grid" />
</Grid>
</Window>
Code-behind:
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DataGridAddColumn
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddColumn(DataGrid grid, string name, int where)
{
if (where == 0)
{
grid.Columns.Insert(0, new DataGridTextColumn{Header = name});
}
else
{
grid.Columns.Add(new DataGridTextColumn { Header = name });
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AddColumn(grid, tbName.Text, cbWhere.SelectedIndex);
}
}
}

Bind XAML elements to entities TwoWay

I want to bind an entity property (say Salary) to a property of a XAML element (like a TextBox.Text)
and use this binding to save Text of TextBox to salary field which is bound as a entity property to 'Text' of some TextBox.
Something like the following :
<Grid DataContext="Employee">
<TextBox Text="{Binding Path=Salary, Mode=TwoWay}"/>
</Grid>
you just can bind Properties in xaml - so your salary have to be a property and not a field. if your Employee is the class with the salary you can set datacontext to an instance of it. you can do it in xaml or codebehind or with binding.
public class Employee //implement INotifyPropertyChanged to get the power of binding :)
{
public decimal Salary {get;set}
}
view.xaml
<Grid>
<Grid.DataContext>
<local:Employee/>
</Grid.DataContext>
<TextBox Text="{Binding Path=Salary, Mode=TwoWay}"/>
</Grid>
you can set the datacontext in many ways
XAML Two-Way Binding, 'Windows Universal' style, step-by-step
In Visual Studio 2017, create a Visual C# blank app (Universal Windows). Name it 'MyProject'.
Add a class Employee to it, and then modify boilerplate code as follows:
// Employee.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MyProject
{
public class Employee : INotifyPropertyChanged
{
private string salary;
public string Salary
{
get
{
return this.salary;
}
set
{
if (value != this.salary)
{
this.salary = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method MUST BE called by the Set accessor of each property for TwoWay binding to work.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// Constructor with one parameter
public Employee(string annualSalary) { salary = annualSalary; }
}
}
Notice that class Employee implements the INotifyPropertyChanged Interface.
Add class EmployeeViewModel to project, and modify boilerplate code as follows:
// EmployeeViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyProject
{
public class EmployeeViewModel
{
private Employee defaultEmployee = new Employee("50000");
public Employee DefaultEmployee { get { return this.defaultEmployee; } }
}
}
Modify MainPage.xaml.cs boilerplate code as follows
//MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace MyProject
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new EmployeeViewModel();
}
public EmployeeViewModel ViewModel { get; set; }
}
}
Modify MainPage.xaml boilerplate code as follows:
<Page
x:Class="MyProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyProject"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--TextBlock will provide visual feedback that the two-way binding is working-->
<TextBlock x:Name="Control" Text="{x:Bind ViewModel.DefaultEmployee.Salary, Mode=OneWay}" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center"/>
<!--TextBox has two-way binding-->
<TextBox x:Name="Input" Text="{x:Bind ViewModel.DefaultEmployee.Salary, Mode=TwoWay}" Margin="10" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center"/>
<!--Button does nothing other than allow TextBox to lose focus-->
<Button x:Name="btn1" Content="Hello" Grid.Column="1" Grid.Row="3"
Foreground="Green"
HorizontalAlignment="Center"/>
</Grid>
</Page>
Notice, both 'Input' TextBox and 'Control' TextBlock are bound to the same Salary property of DefaultEmployee. The idea is that you edit and change the salary in the 'Input' TextBox, and then you can visually see the Two-Way binding at work, because the 'Control' TextBlock will update. This happens when the 'Input' TextBox loses focus (it is to allow the change of focus, for instance after pressing TAB key, that the 'Hello' button was added - the button in itself does absolutely nothing).
Build and Run. Modify salary, and either TAB or click button:
No you cant do like that. You cant Set the Class name to the DataContext. It should be the instance of Employee class.

How to make PanelDragDropTarget act only as a target and not as a source?

I created a new solution. I added a link to System.Windows.Controls.Toolkit to my Silverlight project and wrote this code:
XAML:
<UserControl x:Class="SilverlightApplication4.MainPage"
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:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid>
<toolkit:PanelDragDropTarget Margin="0,0,150,150" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" AllowedSourceEffects="Copy">
<Grid Name="grid1" Background="Blue">
<Rectangle Height="40" HorizontalAlignment="Left" Margin="5,5,0,0" Name="rectangle1" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="80" Fill="Red" />
</Grid>
</toolkit:PanelDragDropTarget>
<toolkit:PanelDragDropTarget VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" Margin="150,150,0,0" AllowDrop="True" Drop="PanelDragDropTarget_Drop" AllowedSourceEffects="None">
<Grid Name="grid2" Background="Green" />
</toolkit:PanelDragDropTarget>
</Grid>
</Grid>
</UserControl>
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication4
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void PanelDragDropTarget_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
Rectangle myRectangle = new Rectangle() { Margin = new Thickness(5,5,0,0), Height = 40, Width = 80,
HorizontalAlignment = System.Windows.HorizontalAlignment.Left, VerticalAlignment = System.Windows.VerticalAlignment.Top,
StrokeThickness = 1, Stroke = new SolidColorBrush(Colors.Black), Fill = new SolidColorBrush(Colors.Red)};
grid2.Children.Add(myRectangle);
}
}
}
Now when I drag and drop the small red rectangle from grid1 onto grid2 everything works fine. But when I touch the new added rectangle in grid2 it shows visible signs that it can be dragged. My question is how to make a second PanelDragDropTarget (with grid2 inside) to act only as a target for drag and drop and not as a source? I mean how to block the possibility for a user to drag the new created rectangle in grid2, i.e. to exclude any visible signs that this new rectangle is draggable? Because it's not supposed to be draggable in my case.
I found a solution. For the PanelDragDropTarget adorner of the grid2 I defined an event handler for its ItemDragStarting event.
private void PanelDragDropTarget_ItemDragStarting(object sender, ItemDragEventArgs e)
{
e.Cancel = true;
e.Handled = true;
}
Now when I try to drag elements in grid2 nothing happens (that was my purpose).
Have you tried AllowedSourceEffects="None", works for me in SL5...

WPF Databinding not calling !

I have UserControl which contains a TextBox and a Button control. The Button opens a FileDialog and the user selects the file. The selected file is transferred into the FileName property which is a dependency property. For some reason the TextBox is not binding to this property. Here is the code:
<UserControl x:Class="WPF3D.FileInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300" x:Name="root">
<Grid>
<StackPanel>
<TextBox Name="txtFile" Text="{Binding FileName, ElementName=root}" Width="300" Height="20" />
<Button Content="Select File" Width="100" Height="20" Click="SelectFile" />
</StackPanel>
</Grid>
</UserControl>
And here is the code for the UserControl.cs file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.Windows.Markup;
namespace WPF3D
{
[ContentProperty("FileName")]
public partial class FileInputBox : UserControl
{
public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register("FileName",
typeof (String),
typeof (FileInputBox));
public event EventHandler<EventArgs> FileNameChanged;
public FileInputBox()
{
InitializeComponent();
txtFile.TextChanged += new TextChangedEventHandler(txtFile_TextChanged);
}
void txtFile_TextChanged(object sender, TextChangedEventArgs e)
{
e.Handled = true;
if(FileNameChanged != null)
{
FileNameChanged(this, EventArgs.Empty);
}
}
public string FileName
{
get { return (string) GetValue(FileNameProperty); }
set { SetValue(FileNameProperty,value);}
}
private void SelectFile(object sender, RoutedEventArgs e)
{
// select the file
var fileDialog = new OpenFileDialog();
fileDialog.ShowDialog();
this.FileName = fileDialog.FileName;
}
protected override void OnContentChanged(object oldContent, object newContent)
{
if(oldContent != null)
throw new InvalidOperationException("You can't change the content");
}
}
}
I think this is just a scoping issue as evident by this output in the debug window:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=root'. BindingExpression:Path=FileName; DataItem=null; target element is 'TextBox' (Name='txtFile'); target property is 'Text' (type 'String')
If you just change it to this, it works fine:
<UserControl x:Class="TestApp.FileInputBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid x:Name="_grid">
<StackPanel>
<TextBox Name="txtFile" Text="{Binding FileName}" Width="300" Height="20" />
<Button Content="Select File" Width="100" Height="20" Click="SelectFile" />
</StackPanel>
</Grid>
</UserControl>
And the important part of the code-behind:
public FileInputBox()
{
InitializeComponent();
txtFile.TextChanged += new TextChangedEventHandler(txtFile_TextChanged);
_grid.DataContext = this;
}
Note that setting the DataContext on the Grid rather than the UserControl is intentional. If you do it at the UserControl level it becomes possible for consumers of your control to break your bindings simply by changing the DataContext of your UserControl.

Resources