C# WPF UI unresponsive when performing drag drop using user control - c#

I am stuck with a C# wpf drag drop issue. I have created a very simple project that includes a User Control, a couple of classes to hold the data and a form to host multiple copies of the user control (using a bound ItemsControl). When I drag the control onto the form the drag drop is triggered, the observablecollection is updated but the UI doesn't reflect the change and future events don't seem to be working. Rolling over the add item button doesn't even show the rollover effect. Sure I am doing something stupid but I can't seem to see what it is.
Code below (mainly from Microsoft Example)
SimpleDataClass
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _groupName = string.Empty;
private ObservableCollection<SimpleSubDataClass> _titles = new ObservableCollection<SimpleSubDataClass>();
public string GroupName
{
get { return _groupName; }
set
{
if (_groupName != value)
{
_groupName = value;
RaisePropertyChangedEvent("GroupName");
}
}
}
public ObservableCollection<SimpleSubDataClass> Titles
{
get { return _titles; }
set
{
if (_titles != value)
{
_titles = value;
RaisePropertyChangedEvent("Titles");
}
}
}
private void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SimpleSubDataClass
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DragDropControl.Model
{
public class SimpleSubDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _title = string.Empty;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
RaisePropertyChanged("Title");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public SimpleSubDataClass(string title)
{
Title = title;
}
}
}
DDControl - XAML
<UserControl x:Class="DragDropControl.DDControl"
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:DragDropControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="CurrentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Name="txtGroupName" Grid.Row="0" Text="{Binding ElementName=CurrentControl, Path=ThisData.GroupName}"/>
<ListBox Name="lstTitles" Grid.Row="1" ItemsSource="{Binding ElementName=CurrentControl, Path=ThisData.Titles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Name="lblTitle" Content="{Binding Title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
DDControl - Code behind
using DragDropControl.Model;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace DragDropControl
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class DDControl : UserControl
{
public static readonly DependencyProperty ThisDataProperty = DependencyProperty.Register( "ThisData",
typeof(SimpleDataClass),
typeof(DDControl),
new PropertyMetadata(new SimpleDataClass()));
public SimpleDataClass ThisData
{
get { return (SimpleDataClass)GetValue(ThisDataProperty); }
set { SetValue(ThisDataProperty, value); }
}
public DDControl()
{
InitializeComponent();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
DataObject data = new DataObject(this.ThisData);
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
protected override void OnGiveFeedback(GiveFeedbackEventArgs e)
{
base.OnGiveFeedback(e);
if (e.Effects.HasFlag(DragDropEffects.Move))
Mouse.SetCursor(Cursors.Pen);
e.Handled = true;
}
}
}
MainWindow - Xaml
<Window x:Class="DragDropUserControlWithTextBox.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:DragDropUserControlWithTextBox"
xmlns:ddc="clr-namespace:DragDropControl;assembly=DragDropControl"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel Name="stkMain" Background="Gray" Orientation="Horizontal" Drop="stkMain_Drop" AllowDrop="true">
<ItemsControl Name="icColumns" Background="Red">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Name="stkItemsControlPanel" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ddc:DDControl Background="{x:Null}" ThisData="{Binding}"/><!-- MouseMove="DDControl_MouseMove" GiveFeedback="DDControl_GiveFeedback"/>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Name="btnAddData" Content="Add Data" Click="btnAddData_Click"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
MainWindow - Code behind
using DragDropControl.Model;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace DragDropUserControlWithTextBox
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<SimpleDataClass> _data = new ObservableCollection<SimpleDataClass>();
public MainWindow()
{
InitializeComponent();
CreateTestData();
}
private void CreateTestData()
{
SimpleDataClass tempSDC1 = new SimpleDataClass();
tempSDC1.GroupName = "First Item";
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_1"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_2"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_3"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_4"));
tempSDC1.Titles.Add(new SimpleSubDataClass("Title 1_5"));
SimpleDataClass tempSDC2 = new SimpleDataClass();
tempSDC2.GroupName = "Second Item";
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_1"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_2"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_3"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_4"));
tempSDC2.Titles.Add(new SimpleSubDataClass("Title 2_5"));
_data.Add(tempSDC1);
_data.Add(tempSDC2);
this.icColumns.ItemsSource = _data;
}
private void stkMain_Drop(object sender, DragEventArgs e)
{
if (e.Handled == false)
{
if (e.Data.GetDataPresent(typeof(SimpleDataClass)))
{
SimpleDataClass tempData = (SimpleDataClass)e.Data.GetData(typeof(SimpleDataClass));
_data.Add(tempData);
}
e.Effects.HasFlag(DragDropEffects.None);
Mouse.SetCursor(Cursors.Arrow);
e.Handled = true;
}
}
private void btnAddData_Click(object sender, RoutedEventArgs e)
{
SimpleDataClass tempData = new SimpleDataClass();
tempData.GroupName = "Amazing Test";
tempData.Titles.Add(new SimpleSubDataClass("AT_1"));
tempData.Titles.Add(new SimpleSubDataClass("AT_2"));
tempData.Titles.Add(new SimpleSubDataClass("AT_3"));
_data.Add(tempData);
}
}
}

I swapped to using the method described in this WPF tutorial on drag and drop. It still had an issue when you dragged and dropped a user control with a textbox on it (would assume it would be for any control that is hitenabled) where it would create a second instance of the item being dragged but that is pretty easy to work around, just set the enabled state to false when detecting the control is about to be dragged and enable it again when it is dropped. Possibly a hack but one that works.

For drag and drop, create a Behavior and handle the events in the Behavior for the avoiding the UI thread freeze scenarios. Check the below link will be useful for your scenario.
https://www.telerik.com/blogs/adding-drag-and-drop-to-wpf-listboxes-thanks-telerik!

Related

Binding not updating WPF

I am trying bind an int to a label in a simple application. The idea is, when a button is pressed, the int, and hence the label, is updated.
I have simplified the code as much as I can but I am not able to see the problem.
I think the issue is with NotifyPropertyChanged(String propertyName) as at runtime start the label's content is updated with the value of the int. However, when the int updates, the label does not.
MainWindow.xaml
<Window x:Class="Press.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Presses: "/>
<Label Content="{Binding Path=PressCount}"/>
</StackPanel>
<Button Content="Press Me" Click="PressMe_Click"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Press
{
public partial class MainWindow : Window
{
public int pressCount = 0;
public int PressCount {
get {
return pressCount;
}
private set {
if(value != pressCount)
{
pressCount = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void PressMe_Click(object sender, RoutedEventArgs e)
{
PressCount++;
Console.WriteLine(PressCount);
}
}
}
You need MainWindow to implement the interface INotifyPropertyChanged

ItemsControl ItemSource Binding not updating

I used to just create a block of text by converting a list of strings to one string with newlines. This Binding worked; updated when it was supposed to and all, but I'm trying to move the list of text into an ItemsControl as they will need to be hyperlinks at some point in the future. Problem: The ItemsControl does not change when the PropertyChangeEvent is fired. The Relevant Code is as follows:
Xaml
<local:BaseUserControl x:Class="BAC.Windows.UI.Views.ErrorsView"
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:BAC.Windows.UI.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
...
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--<TextBlock VerticalAlignment="Center" Visibility="{Binding ErrorMessages, Converter={StaticResource VisibleWhenNotEmptyConverter}}" Text="{Binding ErrorMessages, Converter={StaticResource ErrorMessagesToTextConverter}}">
(What I used to use)
</TextBlock>-->
...
</local:BaseUserControl>
ViewModel
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using ASI.Core.Core;
using ASI.Core.DTO;
using ASI.Core.Extensions;
using ASI.Core.Mappers;
using BAC.Core.Resources;
using BAC.Core.Services;
using BAC.Core.ViewModels.Views;
namespace BAC.Core.ViewModels
{
public interface IErrorsViewModel : IViewModel<IErrorsView>
{
}
public class ErrorsViewModel : BaseViewModel<IErrorsView>, IErrorsViewModel
{
...
private readonly ErrorDTO _errorDTO;
private readonly ErrorDTO _warningDTO;
public ErrorsViewModel(...) : base(view)
{
...
//Just added this string to know that it's at least binding. This Message displays, and never changes.
ErrorMessages = new List<string>() {"Simple Message"};
//Tells the View to bind dataContext to Viewmodel
Edit();
}
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
ErrorDTO dto;
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
ErrorMessages.Clear();
_errorDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Errors + ": " + x));
_warningDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Warnings + ": " + x));
OnPropertyChanged(() => ErrorMessages);
OnPropertyChanged(() => HasError);
OnPropertyChanged(() => HasWarning);
}
...
public bool HasError => _errorDTO.HasError;
public bool HasWarning => _warningDTO.HasError;
public IList<string> ErrorMessages { get; set; }
...
}
And just because I know people may ask to see it...
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var body = propertyExpression.Body as MemberExpression;
if (body != null)
OnPropertyChanged(body.Member.Name);
}
protected void OnEvent(Action action)
{
try
{
action();
}
catch
{ }
}
}
I'm sure it's something stupidy simple I'm doing, but the harder I look, the more I get frusterated by what should something simple. Why does the binding work for all other conrols except ItemSource? What's so special about it?
I'll also add anotehr explanation (Even though I know this is old).
The reason this will not update the property is that the List object is not actually changing, so the ListView will not update the list. The only way to do this without using "ObservableCollection" is to create a brand new list on each property change like so:
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
OnPropertyChanged(() => ErrorMessages);
}
public List<string> ErrorMessages => getErrorMessages();
private List<string> getErrorMessages() {
//create list in a manner of your choosing
}
Hopefully that helps people when they run into this.
So I was able to get your code to work by using an ObservableCollection instead of the List. The ObservableCollection generates a list changed notification automatically when its collection is changed. Below is my sample code. I use a timer to update the error list every second.
<Window x:Class="TestEer.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:TestEer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
using System.Collections.ObjectModel;
using System.Timers;
using System.Windows;
using System.Windows.Data;
namespace TestEer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Timer _timer;
private readonly object _sync = new object( );
public MainWindow( )
{
InitializeComponent( );
BindingOperations.EnableCollectionSynchronization( ErrorMessages, _sync );
_timer = new Timer
{
AutoReset = true,
Interval = 1000
};
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
_timer.Start( );
}
private void _timer_Elapsed( object sender, ElapsedEventArgs e )
{
ErrorMessages.Add( $"Error # {e.SignalTime}" );
}
public ObservableCollection<string> ErrorMessages { get; } = new ObservableCollection<string>( );
}
}
We set up the OnPropertyChanged() method in the get set methods before the constructor and this seemed to work!
private bool _theString;
public bool TheString
{
get { return _theString; }
set { _theString = value; OnPropertyChanged(); }
}
Use {Binding TheString} in your .xaml.
Hope this helps!

WPF Add items to observable collection and print. Returning object reference error

I have a simple app here that uses an observable collection and generates tabcontrol tabs per item. However when I try to add additional tabs it fails. I also noticed when i attempt to print the data in the collection, it appears i can not do that. I get the following error for both lines of code.
Error 1 An object reference is required for the non-static field,
method, or property
InitializeComponent();
//ERRORS on both lines below
Console.WriteLine(ViewModel.TabItems);
ViewModel.AddContentItem();
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//ERRORS on both lines below
Console.WriteLine(ViewModel.TabItems);
ViewModel.AddContentItem();
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="250">
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabControl ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
ViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
}
ObservableObject.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
You should do this instead
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = (ViewModel) this.DataContext;
Debug.WriteLine(viewModel.TabItems);
viewModel.AddContentItem();
}
}
}
Updated thanks King King

Exception Occurring Infinitely in Content Control

First, this is a simplified version from a wizard control using MVVM. The problem is just easier to reproduce as described below
After much narrowing down, I have resolved an infinite exception in my code to be due to the WPF ContentControl. However, I have yet to figure out how to handle it, other than try-catch wrapping all of my possible instantiation code. Here is sample code that reproduces this...any help on how to keep this infinite exception from occurring would be greatly appreciated.
Additional Details
To sum up, the problem is that if the content control changes its contents, and the thing being loaded in throws an exception, then it will throw, then retry the load, causing the throw again and again.
MainWindow.xaml
<Window x:Class="WpfApplication8.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" Name ="Main">
<Grid>
<ContentControl Name="bar" Content="{Binding ElementName=Main, Path=foo}"/>
<Button Click="ButtonBase_OnClick" Margin="20" Width="50"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private UserControl _foo;
public UserControl foo
{
get { return _foo; }
set { _foo = value; OnPropertyChanged("foo"); }
}
public MainWindow()
{
InitializeComponent();
foo = new UserControl1();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
foo = new UserControl2();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
UserControl1 is blank and all default
UserControl2.xaml.cs
public UserControl2()
{
InitializeComponent();
throw new Exception();
}
Do not bind ContentControl to MainWindow. Instead use DataTemplates to select the content for the MainWindow. One example-contrived way of doing it is to bind the ContentControl's Content to the DataContext of the MainWindow.
First some observable test data is needed. The specifics of this data are not important. The main point is to have two different classes of test data from which to choose - TestData.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace fwWpfDataTemplate
{
// Classes to fill TestData
public abstract class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public class Student : Person { }
public class Employee : Person
{
float _salary;
public float Salary
{
get { return _salary; }
set
{
_salary = value;
OnPropertyChanged("Salary");
}
}
}
public class TestData : ObservableCollection<Person>
{
public TestData()
: base(new List<Person>()
{
new Student { Name = "Arnold" },
new Employee { Name = "Don", Salary = 100000.0f }
}) { }
}
}
Then add DataTemplates to MainWindow's resources - MainWindow.xaml:
<Window x:Class="fwWpfDataTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:fwWpfDataTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type me:Student}">
<StackPanel>
<TextBlock Text="Student"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type me:Employee}">
<StackPanel>
<TextBlock Text="Employee"/>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="Salary"/>
<TextBlock Text="{Binding Salary}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Change Data Context" Click="Button_Click" />
<ContentControl Grid.Row="1" Content="{Binding}"/>
</Grid>
</Window>
Note: instead of the StackPanels the contents of the DataTemplates could be UserControl1, UserControl2, etc.
Then add some code to change the data context - MainWindow.cs:
using System.Windows;
namespace fwWpfDataTemplate
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
TestData testData = new TestData();
int testIndex = -1;
private void Button_Click(object sender, RoutedEventArgs e)
{
testIndex = (testIndex + 1) % testData.Count;
this.DataContext = testData[testIndex];
}
}
}
Enjoy.

Scroller-like animation on a Label in WPF

I have a simple app where after clicking a button the value of a label is updated every second.I'm doing this as a POC for a progress bar control that I want to develop.
I would like to know if there is a way to apply some kind of scroller animation to the label which will:
1) When the content of a label is updated it will scroll the new value from the top and the old one will be scrolled down and disappear from view(Hope this makes sence).
I know that this could probably be achieved with some kind of animation but I couldn't find any helpful examples on the web if anyone knows how this can be done please share your expertise:
View:
<Window x:Class="WpfApplication1.ScrollerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Scroller" DataContext="{StaticResource scrollerVM}" Height="150" Width="300">
<Grid>
<ListBox ItemsSource="{Binding Messages}" Width="200" Height="50" BorderThickness="0" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Width="70" Height="24" Content="Add new" Command="{Binding AddNew}" HorizontalAlignment="Left" Margin="0,56,0,30" />
</Grid>
</Window>
View model:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
namespace WpfApplication1.Scroller
{
public class Message
{
public Message(string _text)
{
text = _text;
}
private string text;
public string Text
{
get { return text; }
set {text = value;}
}
}
public class ScrollerViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public DelegateCommand AddNew { get; protected set; }
ObservableCollection<Message> _messages = new ObservableCollection<Message>();
public ObservableCollection<Message> Messages
{
get { return _messages; }
set
{
_messages = value;
OnPropertyChanged("Messages");
}
}
public ScrollerViewModel()
{
AddNew = new DelegateCommand(Add);
}
private void Add(object parameter)
{
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new System.EventHandler(timer_Tick);
timer.Interval = new System.TimeSpan(0, 0, 1);
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
Messages.Clear();
Messages.Add(new Message(DateTime.Now.ToString("ss")));
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
More comprehensive/different examples here.
The following will result in a basic vertical marquee (scrolling text block).
XAML:
<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" Loaded="Window_Loaded">
<Canvas Name="canvas1" >
<TextBlock Name="textBlock1">Hello</TextBlock>
</Canvas>
</Window>
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void BeginAnimation()
{
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = -textBlock1.ActualHeight;
doubleAnimation.To = canvas1.ActualHeight;
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(3));
textBlock1.BeginAnimation(Canvas.TopProperty, doubleAnimation);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BeginAnimation();
}
}
First, you'll want "smooth scrolling" on the ListBox:
ScrollViewer.CanContentScroll="False"
Then, you could create a custom Attached Property to specify the vertical offset you want to scroll. Then create a custom Behavior that hooks up to the ListBox's ItemsSource's "ItemsSourceChanged" event, which would fire off an animation that you can define inside the behavior. That should at least be a start. I'm not sure what the specific animation would be...some DoubleAnimation using a calculation of your offset plus new item's height.

Categories

Resources