Creating a dynamic checkbox list using C# - c#

Im trying to create a list of classes for a degree plan at my university where when classes that have been taken are checked another class gets highlighted to let the user know that class has all of the prerequisites met to be taken. so if i check calculus 1, physics 1 will get highlighted.
Im new to C# and i dont have a heavy knowledge of what the language and .NET framework can do so im asking for a good simple straight answer, if you could explain exactly what is going on in the code that would be fantastic. Thanks
heres what i have so far. just a basic proof of concept WPF
<Window x:Class="degree_plan.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Degree Planner" Height="350" Width="525" Name="Degree">
<Grid>
<CheckBox Content="Math 1412" Height="16" HorizontalAlignment="Left" Margin="34,40,0,0" Name="checkBox1" VerticalAlignment="Top" />
<CheckBox Content="Physics 1911" Height="16" HorizontalAlignment="Left" Margin="34,62,0,0" Name="checkBox2" VerticalAlignment="Top" />
</Grid>
</Window>
and heres the c# code
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;
namespace degree_plan
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// if (checkBox1.Checked)
// Console.WriteLine("physics 1");
}
}
}

You can register an event handler for the checkboxes:
AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckBox_Click));
then, create the event handler:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
CheckBox checkbox = e.Source as CheckBox
//...
}
The checkbox variable in the event handler is the checkbox which was clicked to raise the event. You can check which checkbox it was, and then enable all the options which depend on it.

I know you asked for simple but at some point you can come back to this as it's a very structured and expandable way to hold and use data in WPF
I would consider quantifying the Classes in their own structure, each with a list of the prerequisite classes that must be completed before hand, I would like to suggest using the following to achieve what you're after (sorry, bit long!)
what you'll get is a list of classes represented by checkboxes, you can only check a class once all of its prerequisite classes are complete, they have names and descriptions and can be customised on the UI in anyway you want.
Create a new WPF application and add the following Class.
Class.cs
public class Class : Control, INotifyPropertyChanged
{
// Property that's raised to let other clases know when a property has changed.
public event PropertyChangedEventHandler PropertyChanged;
// Flags to show what's going on with this class.
bool isClassComplete;
bool isPreComplete;
// Some other info about the class.
public string ClassName { get; set; }
public string Description { get; set; }
// A list of prerequisite classes to this one.
List<Class> prerequisites;
// public property access to the complete class, you can only set it to true
// if the prerequisite classes are all complete.
public bool IsClassComplete
{
get { return isClassComplete; }
set
{
if (isPreComplete)
isClassComplete = value;
else
if (value)
throw new Exception("Class can't be complete, pre isn't complete");
else
isClassComplete = value;
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs("IsClassComplete"));
}
}
// public readonly property access to the complete flag.
public bool IsPreComplete { get { return isPreComplete; } }
public Class()
{
prerequisites = new List<Class>();
isPreComplete = true;
}
// adds a class to the prerequisites list.
public void AddPre(Class preClass)
{
prerequisites.Add(preClass);
preClass.PropertyChanged += new PropertyChangedEventHandler(preClass_PropertyChanged);
ValidatePre();
}
// removes a class from the prerequisites lists.
public void RemovePre(Class preClass)
{
prerequisites.Remove(preClass);
preClass.PropertyChanged -= new PropertyChangedEventHandler(preClass_PropertyChanged);
ValidatePre();
}
// each time a property changes on one of the prerequisite classes this is run.
void preClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "IsClassComplete":
// check to see if all prerequisite classes are complete/
ValidatePre();
break;
}
}
void ValidatePre()
{
if (prerequisites.Count > 0)
{
bool prerequisitesComplete = true;
for (int i = 0; i < prerequisites.Count; i++)
prerequisitesComplete &= prerequisites[i].isClassComplete;
isPreComplete = prerequisitesComplete;
if (!isPreComplete)
IsClassComplete = false;
}
else
isPreComplete = true;
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs("IsPreComplete"));
}
}
Now in the code behind for MainWindow.cs you can create a collection of classes, I've done this in the constructor and provided an observable collection of classes so when new classes are added, you don't have to do anything to get them to display on the UI
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Class> Classes
{
get { return (ObservableCollection<Class>)GetValue(ClassesProperty); }
set { SetValue(ClassesProperty, value); }
}
public static readonly DependencyProperty ClassesProperty = DependencyProperty.Register("Classes", typeof(ObservableCollection<Class>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
Class math = new Class()
{
ClassName = "Math 1412",
Description = ""
};
Class physics = new Class()
{
ClassName = "Physics 1911",
Description = "Everything everywhere anywhen",
};
physics.AddPre(math);
Classes = new ObservableCollection<Class>();
Classes.Add(math);
Classes.Add(physics);
}
}
The final step is to tell WPF what a class is supposed to look like on the user interface, this is done in resources, to simplify the example, I've put it in the MainWindow.xaml file.
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication8"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- This tells WPF what a class looks like -->
<Style TargetType="{x:Type local:Class}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Class}">
<StackPanel Orientation="Horizontal" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<!-- Checkbox and a text label. -->
<CheckBox IsEnabled="{Binding IsPreComplete}" IsChecked="{Binding IsClassComplete}" />
<TextBlock Margin="5,0,0,0" Text="{Binding ClassName}" ToolTip="{Binding Description}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!-- This draws the list of classes from the property in MainWindow.cs-->
<ItemsControl ItemsSource="{Binding Classes}"/>
</Grid>
</Window>

For your ease,
try to use the 'CheckedChanged' event.. Just double click the CheckBox on the designer. Handler will be added automatically(something like,
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
}
.Then add your code there. But, this is time consuming(since, you have to add handler for each CheckBox). But, 'll be easy for you to understand at this stage.

Related

Xceed WPF Toolkit - BusyIndicator - creating dynamic loading messages in C#, data binding in a data template

I have a small WPF app that I am working on that uses the Xceed BusyIndicator. I'm having some trouble dynamically updating the loading message because the content is contained within a DataTemplate. The methods I'm familiar with for data binding, or setting the value of text aren't working.
I've done some research - and it looks like others have had this issue. It seems it was answered here, but because the answer was not in context I cannot quite figure out how that would work in my code.
Here is my sample code, if someone could help me understand what I'm missing I would greatly appreciate it. This has the added challenge of using a BackgroundWorker thread. I use this because I anticipate this will be a long running progress - ultimately the action will start a SQL Job that will process items that may take up to 15 minutes. My plan is to have the thread periodically run a stored procedure to get a count of remaining items to process and update the loading message.
MainWindow.xaml:
<Window x:Class="WPFTest.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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:WPFTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<xctk:BusyIndicator x:Name="AutomationIndicator">
<xctk:BusyIndicator.BusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="Sending Invoices" FontWeight="Bold" HorizontalAlignment="Center"/>
<WrapPanel>
<TextBlock Text="Items remaining: "/>
<TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
</WrapPanel>
</StackPanel>
</DataTemplate>
</xctk:BusyIndicator.BusyContentTemplate>
<Grid>
<StackPanel>
<TextBlock Text="Let's test this thing" />
<Button x:Name="_testBtn" Content="Start" Width="100" HorizontalAlignment="Left" Click="testBtn_Click"/>
<TextBlock Text="{Binding ItemsRemaining}"/>
</StackPanel>
</Grid>
</xctk:BusyIndicator>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
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 WPFTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
UpdateItemsRemaining(0);
}
public class ItemCountDown
{
//One Idea was to try and set a data binding variable
public string ItemsRemaining { get; set; }
}
public void UpdateItemsRemaining(int n)
{
ItemCountDown s = new ItemCountDown();
{
s.ItemsRemaining = n.ToString();
};
//this.AutomationIndicator.DataContext = s; Works during initiation, but not in the thread worker.
}
private void testBtn_Click(object sender, RoutedEventArgs e)
{
//Someone clicked the button, run the Test Status
TestStatus();
}
public void TestStatus()
{
// Normally I'd start a background worker to run a loop to check that status in SQL
BackgroundWorker getStatus = new BackgroundWorker();
getStatus.DoWork += (o, ea) =>
{
//Normally there's a sql connection being opened to check a SQL Job, and then I run a loop that opens the connection to check
//the status until it either fails or successfully ended.
//but for this test, I'll just have it run for 15 seconds, counting down fake items.
int fakeItems = 8;
do //
{
//Idea One - write to the text parameter. But can't find it in the template
//Dispatcher.Invoke((Action)(() => _ItemsRemaining.Text = fakeItems));
//Idea two - use data binding to update the value. Data binding works just find outside of the Data Template but is ignored in the template
UpdateItemsRemaining(fakeItems);
//subtract one from fake items and wait a second.
fakeItems--;
Thread.Sleep(1000);
} while (fakeItems > 0);
};
getStatus.RunWorkerCompleted += (o, ea) =>
{
//work done, end it.
AutomationIndicator.IsBusy = false;
};
AutomationIndicator.IsBusy = true;
getStatus.RunWorkerAsync();
}
}
}
Thank you for reviewing and I appreciate any help or direction given.
Set the DataContext to your ItemCountDown object and implement INotifyPropertyChanged:
public class ItemCountDown : INotifyPropertyChanged
{
private string _itemsRemaining;
public string ItemsRemaining
{
get { return _itemsRemaining; }
set { _itemsRemaining = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public partial class MainWindow : Window
{
private readonly ItemCountDown s = new ItemCountDown();
public MainWindow()
{
InitializeComponent();
DataContext = s;
UpdateItemsRemaining(0);
}
public void UpdateItemsRemaining(int n)
{
s.ItemsRemaining = n.ToString();
}
private void testBtn_Click(object sender, RoutedEventArgs e)
{
TestStatus();
}
public void TestStatus()
{
...
}
}
You can then bind directly to the property in the DataTemplate of the XAML markup:
<TextBlock x:Name="_ItemsRemaining" Text="{Binding Path=DataContext.ItemsRemaining, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>

WPF - Force Binding on Invisible ComboBox

I have a WPF window that contains multiple user controls, some of which are invisible (Visibility = Hidden). One of these controls has a ComboBox that has an ItemsSource binding, and I want to preset its selected item while the window/control is loading.
However, it seems like the binding is not applied until the combobox is visible. When I go to set the SelectedItem property and I hit a breakpoint in the debugger, I notice that ItemsSource is null at that moment. Is there a way to force WPF to apply the data binding and populate the combobox while it stays invisible?
Reproducible Example:
MainWindow.xaml
<Window x:Class="HiddenComboBoxBinding.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:HiddenComboBoxBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Border x:Name="comboboxParent" Visibility="Collapsed">
<ComboBox x:Name="cmbSearchType" SelectedIndex="0" ItemsSource="{Binding SearchTypeOptions}" DisplayMemberPath="Name" SelectionChanged="cmbSearchType_SelectionChanged" />
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace HiddenComboBoxBinding
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel { get; set; } = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
// Add some or all of our search types - in the real code, there's some business logic here
foreach (var searchType in SearchType.AllSearchTypes)
{
viewModel.SearchTypeOptions.Add(searchType);
}
// Pre-select the last option, which should be "Bar"
cmbSearchType.SelectedItem = SearchType.AllSearchTypes.Last();
}
private void cmbSearchType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
}
ViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HiddenComboBoxBinding
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<SearchType> SearchTypeOptions { get; set; } = new ObservableCollection<SearchType>();
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class SearchType
{
// Source list of Search Types
private static List<SearchType> _AllSearchTypes;
public static List<SearchType> AllSearchTypes
{
get
{
if(_AllSearchTypes == null)
{
_AllSearchTypes = new List<SearchType>();
_AllSearchTypes.Add(new SearchType() { Name = "Foo" });
_AllSearchTypes.Add(new SearchType() { Name = "Bar" });
}
return _AllSearchTypes;
}
}
// Instance properties - for the purposes of a minimal, complete, verifiable example, just one property
public string Name { get; set; }
}
}
I was able to figure out the issue. Setting the SelectedItem actually did work (even though ItemsSource was null at that time) but in the XAML, the ComboBox had SelectedIndex="0" and it was taking precedence over the SelectedItem being set in the code-behind.

See UI changes in design view with WPF & XAML and data binding?

I was just watching this video on XAML where he created a clock and noticed he could actually see all the changes he is doing from C Sharp in the Xaml design view.
At 33:30 he creates his class: https://youtu.be/Wb-l0e6WYE0?t=2008
At 37:10 he binds to that class: https://youtu.be/Wb-l0e6WYE0?t=2227
Later on at 40:17 you can actually see the clock is ticking in the design view!
I tried to do this by creating a class called Ball with some properties like size and bind those properties to a Canvas with a rectangle that has a EllipseGeometry clip which makes it round. It works fine when running the application but the design view is just white.
Does anyone know how he does this?
My Code:
MainWindow.xaml
<Window x:Class="XamlTest.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:XamlTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Background="White">
<Rectangle Height="{Binding Size}" Width="{Binding Size}" Fill="Green" Canvas.Top="40">
<Rectangle.Clip>
<EllipseGeometry Center="{Binding EllipseCenter}" RadiusX="{Binding EllipseRadius}" RadiusY="{Binding EllipseRadius}"/>
</Rectangle.Clip>
</Rectangle>
<Button x:Name="button" Content="Button" Width="75" Click="button_Click"/>
</Canvas>
Code Behind:
using System.Windows;
namespace XamlTest
{
public partial class MainWindow : Window
{
Ball TheBall = new Ball();
public MainWindow()
{
InitializeComponent();
TheBall.Size = 300;
this.DataContext = TheBall;
}
private void button_Click(object sender, RoutedEventArgs e)
{
TheBall.Size = TheBall.Size + 40;
}
}
}
Ball Class:
using System.Windows;
namespace XamlTest
{
class Ball : INotifyPropertyChangedBase
{
public Ball()
{
Size = 50;
}
private double _size;
public double Size
{
get
{
return _size;
}
set
{
_size = value;
EllipseCenter = new Point(_size / 2, _size / 2);
EllipseRadius = _size / 2;
OnPropertyChanged("Size");
}
}
private Point _ellipseCenter;
public Point EllipseCenter
{
get
{
return _ellipseCenter;
}
set
{
_ellipseCenter = value;
OnPropertyChanged("EllipseCenter");
}
}
private double _ellipseRadius;
public double EllipseRadius
{
get {
return _ellipseRadius;
}
set {
_ellipseRadius = value;
OnPropertyChanged("EllipseRadius");
}
}
}
}
INotifyPropertyChangedBase Class:
using System.ComponentModel;
namespace XamlTest
{
public class INotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
internal void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I also have a button that increase the size of the ball!
Thanks for the help.
The DataContext allows the XAML to find an instance the class that it is meant to be binding to.
Then, the bindings in XAML allow you to bind to specific properties of said class.
There are two separate DataContexts: design time and run time.
To set the design time DataContext, see:
http://adamprescott.net/2012/09/12/design-time-data-binding-in-wpf/
Essentially, when you set the design time DataContext, behind the scenes the WPF runtime will automatically instantiate a new instance of the class you point it at (or simply point at the class if its static), and then the Visual Studio design time designer will display live values from your class, as you are editing the XAML. This makes designing really fast, as you can work with live data, and you dont have to run the program all the time to see how it looks.
To set the run time DataContext, see ReSharper WPF error: "Cannot resolve symbol "MyVariable" due to unknown DataContext". The answer describes how to use the free Snoop utility to detect runtime binding errors.
Added this code:
d:DataContext="{d:DesignInstance local:Ball,IsDesignTimeCreatable=True}"
And now I can see my green ball at design time!
Thanks!

Creating a single controller for multiple WPF Pages

I'm very new to WPF and a beginner in C#.NET. I'm currently making an application where there will be many pages and the trigger to change the page is hand gesture using Kinect SDK (the trigger method is not relevant for this question). Normally when a WPF file is created, there will be a similarly named .cs file attached to it, which acts somewhat like a controller. However, I need multiple WPF files/pages to be controlled only by a single controller .cs file. How do I achieve that? Thanks for viewing my question and your answer will be very appreciated :)
You probably want to write a class that contains your 'controller' code and reference it from your WPF UserControls / Pages.
In a new file:
public class MyController
{
public void DoThings(object parameter)
{
// stuff you want to do
}
}
and then inside your UserControl code-behind class:
public partial class MyWpfControl : UserControl
{
private MyController controller;
public MyWpfControl
{
this.controller = new MyController();
}
}
and finally, tie your events back to the controller's method:
private void OnGesture(object sender, EventArgs e)
{
// call the method on the controller, and pass whatever parameters you need...
this.controller.DoThings(e);
}
The code behind is really part of the view and isn't really analogous to a controller and generally there shouldn't be much code in them. Typically you would want most of your logic between your "View Model" which serves as an abstraction of the view and "Model" which serves as an abstraction of the business logic that your UI is interacting with.
In this light what I think you really want is a View Model(VM) that controls multiple views. This is a fairly typical scenario and the preferred method (IMO anyway) is to have a hierarchical view model that has a top level the application model and a number of sub VMs that represent different components within your UI, though you can bind everything to your top level VM if you really want to.
To do this we would first define our view model like so
public interface IGestureSink
{
void DoGesture();
}
public class MyControlVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ApplicationVM parent;
public MyControlVM(ApplicationVM parent)
{
this.Name = "my user control";
this.parent = parent;
parent.PropertyChanged += (s, o) => PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public String Name { get; set; }
public bool Visible { get { return parent.ControlVisible; } }
public void DoGesture()
{
parent.DoGesture();
}
}
public class ApplicationVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ApplicationVM()
{
this.ControlVM = new MyControlVM(this);
this.ControlVisible = false;
}
public MyControlVM ControlVM { get; private set; }
public bool ControlVisible {get; set;}
public void DoGesture()
{
this.ControlVisible = !this.ControlVisible;
PropertyChanged(this, new PropertyChangedEventArgs("ControlVisible"));
}
}
and then all we need to do is to build a user control
<UserControl x:Class="WpfApplication2.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightBlue">
<Label Content="{Binding Name}"/>
</Grid>
</UserControl>
and page
<Window xmlns:my="clr-namespace:WpfApplication2" x:Class="WpfApplication2.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">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding ControlVM}" Visibility="{Binding Visible,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="222,262,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
That use it. The only thing that we need in our code behind is a constructor that sets up the page VM and wiring from our button to the view model.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ApplicationVM();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
((IGestureSink)(this.DataContext)).DoGesture();
}
}
If you wanted to use a monolithic view model instead you would use this Instead of binding the DataContext to ControlVM:
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding DataContext}" Visibility="{Binding ControlVisible,Converter={StaticResource BooleanToVisibilityConverter}}"/>

Dynamically set TextBlock's text binding

I am attempting to write a multilingual application in Silverlight 4.0 and I at the point where I can start replacing my static text with dynamic text from a SampleData xaml file. Here is what I have:
My Database
<SampleData:something xmlns:SampleData="clr-namespace:Expression.Blend.SampleData.MyDatabase" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<SampleData:something.mysystemCollection>
<SampleData:mysystem ID="1" English="Menu" German="Menü" French="Menu" Spanish="Menú" Swedish="Meny" Italian="Menu" Dutch="Menu" />
</SampleData:something.mysystemCollection>
</SampleData:something>
My UserControl
<UserControl
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"
x:Class="Something.MyUC" d:DesignWidth="1000" d:DesignHeight="600">
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource MyDatabase}}">
<Grid Height="50" Margin="8,20,8,0" VerticalAlignment="Top" d:DataContext="{Binding mysystemCollection[1]}" x:Name="gTitle">
<TextBlock x:Name="Title" Text="{Binding English}" TextWrapping="Wrap" Foreground="#FF00A33D" TextAlignment="Center" FontSize="22"/>
</Grid>
</Grid>
</UserControl>
As you can see, I have 7 languages that I want to deal with. Right now this loads the English version of my text just fine. I have spent the better part of today trying to figure out how to change the binding in my code to swap this out when I needed (lets say when I change the language via drop down).
It sounds like you're looking for code like this:
Title.SetBinding(TextProperty, new Binding { Path = new PropertyPath(language) });
All it does is create a new Binding for the language you requested and use it to replace the old binding for the Title's Text property.
You are going about this the wrong way. Best practice for localization in Silverlight is to use resource files holding the translated keywords. Here is some more info about this:
http://msdn.microsoft.com/en-us/library/cc838238%28VS.95%29.aspx
EDIT:
Here is an example where I use a helper class to hold the translated strings. These translations could then be loaded from just about anywhere. Static resource files, xml, database or whatever. I made this in a hurry, so it is not very stable. And it only switches between english and swedish.
XAML:
<UserControl x:Class="SilverlightApplication13.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:local="clr-namespace:SilverlightApplication13"
mc:Ignorable="d"
d:DesignWidth="640"
d:DesignHeight="480">
<UserControl.Resources>
<local:TranslationHelper x:Key="TranslationHelper"></local:TranslationHelper>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBlock Margin="10"
Text="{Binding Home, Source={StaticResource TranslationHelper}}"></TextBlock>
<TextBlock Margin="10"
Text="{Binding Contact, Source={StaticResource TranslationHelper}}"></TextBlock>
<TextBlock Margin="10"
Text="{Binding Links, Source={StaticResource TranslationHelper}}"></TextBlock>
<Button Content="English"
HorizontalAlignment="Left"
Click="BtnEnglish_Click"
Margin="10"></Button>
<Button Content="Swedish"
HorizontalAlignment="Left"
Click="BtnSwedish_Click"
Margin="10"></Button>
</StackPanel>
</Grid>
</UserControl>
Code-behind + TranslationHelper class:
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;
using System.Windows.Threading;
using System.ComponentModel;
namespace SilverlightApplication13
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
//Default
(this.Resources["TranslationHelper"] as TranslationHelper).SetLanguage("en-US");
}
private void BtnEnglish_Click(object sender, RoutedEventArgs e)
{
(this.Resources["TranslationHelper"] as TranslationHelper).SetLanguage("en-US");
}
private void BtnSwedish_Click(object sender, RoutedEventArgs e)
{
(this.Resources["TranslationHelper"] as TranslationHelper).SetLanguage("sv-SE");
}
}
public class TranslationHelper : INotifyPropertyChanged
{
private string _Contact;
/// <summary>
/// Contact Property
/// </summary>
public string Contact
{
get { return _Contact; }
set
{
_Contact = value;
OnPropertyChanged("Contact");
}
}
private string _Links;
/// <summary>
/// Links Property
/// </summary>
public string Links
{
get { return _Links; }
set
{
_Links = value;
OnPropertyChanged("Links");
}
}
private string _Home;
/// <summary>
/// Home Property
/// </summary>
public string Home
{
get { return _Home; }
set
{
_Home = value;
OnPropertyChanged("Home");
}
}
public TranslationHelper()
{
//Default
SetLanguage("en-US");
}
public void SetLanguage(string cultureName)
{
//Hard coded values, need to be loaded from db or elsewhere
switch (cultureName)
{
case "sv-SE":
Contact = "Kontakt";
Links = "Länkar";
Home = "Hem";
break;
case "en-US":
Contact = "Contact";
Links = "Links";
Home = "Home";
break;
default:
break;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Categories