How to two way bind multiple strings to multiple textboxes in WPF? - c#

I have an application that receives from the user a list of string.
The application needs to display that list and provide a TextBox per each string (for additional user input).
Eventually the application will gather the values from the TextBox elements, one value per user input-string.
The layout of the application is a Grid based layout. Here is a mockup example developed in Python tkinter:
I am trying to build the same thing in WPF and can't find the way to correctly bind the items.
I tried multiple approaches, none results in a working window.
Here is my latest version:
XAML
<Grid x:Name="MainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20*"/>
<RowDefinition Height="10"/>
<RowDefinition Height="20"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="86"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Opcode" Grid.Column="0" Grid.Row="0" VerticalAlignment="Bottom" FontFamily="Courier New" HorizontalAlignment="Center"/>
<Label Content="Operand" Grid.Column="1" Grid.Row="0" VerticalAlignment="Bottom" FontFamily="Courier New" HorizontalAlignment="Center"/>
<ComboBox x:Name="Opcodes" Grid.Column="0" Grid.Row="1" Height="22" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center" IsTextSearchEnabled="False" SelectionChanged="Opcodes_SelectionChanged"/>
<TextBox x:Name="Operand" Grid.Column="1" Grid.Row="1" Height="20" Width="70" HorizontalAlignment="Center" VerticalAlignment="Center" IsUndoEnabled="False" IsEnabled="False"/>
<Label Content=">" Grid.Column="2" Grid.Row="1" FontFamily="Courier New" FontSize="11" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<ItemsControl x:Name="PinGrid" Grid.Row="0" Grid.Column="3" Margin="0,0,0,4" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" Margin="2" Width="14" FontSize="14" FontWeight="Medium" FontFamily="Courier New" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl x:Name="PinDataGrid" Grid.Row="1" Grid.Column="3" IsTabStop="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<WrapPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Center">
<Button x:Name="Cancel" Content="_Cancel" IsCancel="True" Width="50" Click="Cancel_Click"/>
<Button x:Name="Ok" Content="_Ok" Margin="10,0,0,0" IsDefault="True" Width="50" Click="Ok_Click"/>
</WrapPanel>
</Grid>
Code Behind
private ObservableCollection<string> Pins { get; set; } = null;
private List<string> PinsData { get; set; } = null;
private string OperandData { get; set; } = null;
(window c-tor)
Pins = new ObservableCollection<string>(<user provided list of pins>);
PinGrid.ItemsSource = Pins;
PinsData = new List<string>();
for(int i = 0; i < Pins.Count; i++)
{
PinsData.Add("X");
TextBox t = new TextBox()
{
Margin = new Thickness(2, 0, 2, 0),
Padding = new Thickness(0),
Width = 14,
FontSize = 13,
FontWeight = FontWeights.Medium,
FontFamily = new FontFamily("Courier New"),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center
};
Binding b = new Binding();
b.Mode = BindingMode.TwoWay;
b.Path = new PropertyPath("PinsData[" + i + "]");
t.SetBinding(TextBox.TextProperty, b);
PinDataGrid.Items.Add(t);
}
Opcodes.Items.Add(String.Empty); // add empty entry for clear operation
foreach (string opcode in <user provided list of opcodes>)
Opcodes.Items.Add(opcode);
DataContext = this;
Here is the WPF version:
As you can see, the X is not showing in the TextBox and the binding didn't happen. What am I doing wrong? How can I create the two way binding of text boxes to the PinsData list of strings?
It might be that my whole approach is wrong - so such kinds of inputs will be more than welcomed.

I'll try to tell you why it didn't work for you first.
In C# strings are immutable so, as you were binding using a two-way mode, every time you changed the string it was a different object instance. Try changing your b.Mode = BindingMode.TwoWayto OneWay and you will see the "X" there, but it won't work for what you want.
Managed to get the link I used once when I faced a similar issue
binding string
To achieve what you want you should do the following:
Create a new class to control your item changes like this:
public class PinsDataItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
string text;
public PinsDataItem(string value)
{
this.Text = value;
}
public string Text
{
get
{
return text;
}
set
{
if (text != value)
{
text = value;
}
NotifyPropertyChanged("Value");
}
}
}
Your forshould change to this:
PinsData = new List<PinsDataItem>();
for (int i = 0; i < Pins.Count; i++)
{
PinsData.Add(new PinsDataItem("X"));
TextBox t = new TextBox()
{
Margin = new Thickness(2, 0, 2, 0),
Padding = new Thickness(0),
Width = 14,
FontSize = 13,
FontWeight = FontWeights.Medium,
FontFamily = new FontFamily("Courier New"),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center
};
Binding b = new Binding();
b.Mode = BindingMode.TwoWay;
b.Source = PinsData[i];
b.Path = new PropertyPath("Text");
t.SetBinding(TextBox.TextProperty, b);
PinDataGrid.Items.Add(t);
}
This should work for you. Let me know if it didn't :D

Related

How to show custom labels and status on custom appointment window using WPF DevExpress and MVVM

I'm using a Scheduler Control in which I want to create a custom appointment window. My Scheduler Control looks like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<dxsch:SchedulerControl x:Name="scheduler" ActiveViewType="WeekView" FirstDayOfWeek="Monday" Grid.Column="0">
<dxsch:SchedulerControl.OptionsWindows>
<dxsch:OptionsWindows AppointmentWindowType="{x:Type local:CrearTareaWindow}"/>
</dxsch:SchedulerControl.OptionsWindows>
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="AppointmentAdded" Command="{Binding SaveCommand}" />
<dxmvvm:EventToCommand Command="{Binding DeleteCommand}" EventName="AppointmentRemoved"/>
<dxmvvm:EventToCommand Command="{Binding EditCommand}" EventName="AppointmentEdited" />
</dxmvvm:Interaction.Behaviors>
<dxsch:SchedulerControl.DataSource>
<dxsch:DataSource AppointmentsSource="{Binding Tareas}" AppointmentLabelsSource="{Binding Labels}" AppointmentStatusesSource="{Binding Status}">
<dxsch:DataSource.AppointmentMappings>
<dxsch:AppointmentMappings
Subject="nombre"
Description="descripcion"
Start="fechaInicio"
End="fechaFin">
<dxsch:CustomFieldMapping Mapping="custom" Name="custom" />
</dxsch:AppointmentMappings>
</dxsch:DataSource.AppointmentMappings>
</dxsch:DataSource>
</dxsch:SchedulerControl.DataSource>
</dxsch:SchedulerControl>
<dxe:DateNavigator Name="dateNavigator" Grid.Column="1" ShowTodayButton="False">
<dxe:DateNavigator.StyleSettings>
<dxsch:SchedulerDateNavigatorStyleSettings Scheduler="{Binding ElementName=scheduler}" />
</dxe:DateNavigator.StyleSettings>
</dxe:DateNavigator>
</Grid>
And my custom appointment window looks like this:
<StackPanel Margin="10">
<TextBlock FontWeight="Bold" Text="Nombre:"/>
<TextBox Text="{Binding Subject, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock FontWeight="Bold" Text="DescripciĆ³n:"/>
<TextBox Text="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="100"/>
<TextBlock FontWeight="Bold" Text="Fecha de Inicio:"/>
<DockPanel>
<dxe:DateEdit
x:Name="editorStartDate"
Width="150"
DockPanel.Dock="Left"
Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_StartDate}}" />
<dxe:TextEdit
x:Name="editorStartTime"
Margin="4,0,0,0"
DockPanel.Dock="Left"
Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_StartTime}}" />
</DockPanel>
<TextBlock FontWeight="Bold" Text="Fecha de fin:"/>
<DockPanel>
<dxe:DateEdit
x:Name="editorEndDate"
Width="150"
DockPanel.Dock="Left"
Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_EndDate}}" />
<dxe:TextEdit
x:Name="editorEndTime"
Margin="4,0,0,0"
DockPanel.Dock="Left"
Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_EndTime}}" />
</DockPanel>
<TextBlock Text="Etiqueta:" FontWeight="Bold"/>
<dxsch:AppointmentLabelEdit/>
<TextBlock Text="Estatus:" FontWeight="Bold"/>
<dxsch:AppointmentStatusEdit/>
<TextBlock FontWeight="Bold" Text="Custom:"/>
<TextBox Text="{Binding CustomFields.custom, Mode=TwoWay}"/>
<Grid Margin="0 10">
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0 0 10 0"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="GUARDAR" Command="{Binding SaveAndCloseAppointmentCommand}"/>
<Button Grid.Column="1"
Content="BORRAR"
CommandParameter="{Binding SelectedAppointments[0], ElementName=scheduler}"
Command="{Binding RemoveAppointmentCommand}"/>
<Button Grid.Column="2" Content="CANCELAR" Command="{Binding CancelEditingCommand}"/>
</Grid>
</StackPanel>
In the custom appointment window I'm creating a AppointmentLabelEdit and AppointmentStatusEdit control to show my custom labels and statuses, the problem is that they are not showing. I have binded the AppointmentLabelsSource and AppointmentStatusSource to the Scheduler Control DataSource as the documentation suggest, but I don't find a way to show my custom labels in the custom appointment window.
The view model to which the SchedulerControl window is bindend looks like this:
public class ViewModel: ViewModelBase {
public ObservableCollection < Tarea > Tareas {
get;
set;
} = new ObservableCollection < Tarea > ();
public ObservableCollection < CustomLabel > Labels {
get;
set;
} = new ObservableCollection < CustomLabel > ();
public ObservableCollection < CustomStatus > Status {
get;
set;
} = new ObservableCollection < CustomStatus > ();
public ViewModel() {
Tareas.Add(new Tarea {
nombre = "Cita con el doctor",
descripcion = "Al PPL le duele la panza",
fechaInicio = new DateTime(2020, 10, 1, 12, 0, 0),
fechaFin = new DateTime(2020, 10, 1, 14, 0, 0),
EtiquetaId = 2,
EstatusId = 1
});
Labels.Add(new CustomLabel {
Id = 1,
Caption = "DOCTOR",
Color = Color.Blue
});
Labels.Add(new CustomLabel {
Id = 2,
Caption = "GUARDIA",
Color = Color.Green
});
Status.Add(new CustomStatus {
Id = 1,
Caption = "PENDIENTE",
Brush = Brushes.AliceBlue
});
Status.Add(new CustomStatus {
Id = 2,
Caption = "TERMINADA",
Brush = Brushes.OrangeRed
});
}
}
What is the way to show the custom labels and statuses in my custom window?
You should use mapping.
<dxsch:DataSource.AppointmentLabelMappings>
<dxsch:AppointmentLabelMappings Color="Color" Caption="Caption" Id="Id" />
</dxsch:DataSource.AppointmentLabelMappings>
<dxsch:DataSource.AppointmentStatusMappings>
<dxsch:AppointmentStatusMappings Brush="Brush" Caption="Caption" Id="Id" />
</dxsch:DataSource.AppointmentStatusMappings>
Devexpress documentation (Labels, Statuses, Example).

Stop sections from being cut off when xps file is created on WPF

The Application
I am working on an EHR application. The page I am working on converts the contents of a XAML file's data to an XPS file which can be printed or drawn on. When the page's data in on the XAML page it can easily be scrolled through and the data is kept inside a BlockUIContainer. However once the XPS file is generated, some sections are cut off.
Application Code
Inside the 10-14YearVisit form XAML file
<!--History Section-->
<BlockUIContainer>
<genForms_Controls:PatientHistoryUC />
</BlockUIContainer>
<!--History Bottom-->
The PatientHistoryUC.xaml (Sorry, lots of code!)
<UserControl x:Class="Pcis.Emr.GeneralForms.Controls.PatientHistoryUC"
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:genForms_Controls="clr-namespace:Pcis.Emr.GeneralForms.Controls"
xmlns:galenControls="clr-namespace:Pcis.Emr.GeneralForms.Galen.Controls"
xmlns:util="clr-namespace:Pcis.Emr.Common.Utils;assembly=Pcis.Emr.Common"
xmlns:sectionDatas="clr-namespace:Pcis.Emr.Data.SectionDatas;assembly=Pcis.Emr.Data"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Border BorderBrush="Black"
BorderThickness="1"
CornerRadius="5"
Width="790"
Margin="0,0,2,5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--past medical history-->
<galenControls:GalenPastMedicalHistory Grid.ColumnSpan="2"
util:FormTemplateSectionUtil.ShowInForm="{x:Static sectionDatas:SectionType.ProblemList}"
util:FormTemplateSectionUtil.TemplateId="{Binding TemplateId}"
Margin="3"/>
<genForms_Controls:ProblemsSection PMHFontSize="11"
Margin="3"
Grid.Row="1"
Grid.ColumnSpan="2"
util:FormTemplateSectionUtil.ShowInForm="{x:Static sectionDatas:SectionType.Problems}"
util:FormTemplateSectionUtil.TemplateId="{Binding TemplateId}" />
<StackPanel Grid.Row="2"
Margin="3">
<!--past surgical history-->
<Button Grid.Column="0"
VerticalAlignment="Top"
Style="{StaticResource NoBorderButtonHand}"
Command="{Binding PickPSHCommand, Mode=OneWay}"
HorizontalAlignment="Left">
<TextBlock VerticalAlignment="Center"
FontWeight="Bold"
Text="Past Surgical History"
TextDecorations="Underline"
HorizontalAlignment="Left"
Foreground="Blue" />
</Button>
<ItemsControl Grid.Column="0"
Margin="0,0,6,0"
ItemTemplate="{DynamicResource PSHItemTemplate}"
ItemsSource="{Binding PastSurgicalHistoryChronicOnly, Mode=Default}"
VerticalAlignment="Top" />
</StackPanel>
<StackPanel x:Name="FH_SH"
Grid.Column="4"
Grid.Row="2"
Margin="3"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<!--Family history-->
<galenControls:GalenFamilyHistory />
<genForms_Controls:SocialHistoryUCTreeView />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
</StackPanel>
<StackPanel x:Name="PMH_PSH"
Grid.Column="0"
Grid.Row="2"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Width="390.423"
Margin="3">
<Grid Margin="0,5,0,0"
Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.799*" />
<ColumnDefinition Width="0.201*" />
</Grid.ColumnDefinitions>
<!--current medications-->
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Margin="0,0,8,0"
HorizontalAlignment="Right"
Width="249" />
</Grid>
</StackPanel>
</Grid>
</Border>
</UserControl>
xps file generation
public bool SaveDocument(
out string path,
out string error,
bool digitallySign = true,
SerializerWriterCollator customCollator = null,
PageRange? pr = null,
string headerText = null,
bool printAccount = true,
HorizontalAlignment patientAlignment = HorizontalAlignment.Center,
HorizontalAlignment headerTextAlignment = HorizontalAlignment.Center,
HorizontalAlignment logoAlignment = HorizontalAlignment.Right,
int headerFontSize = 13,
Typeface headerTypeFace = null,
string generatedNote = null,
bool alwaysThrow = false)
{
path = Util.GetTempFile("xps");
error = string.Empty;
SerializerWriterCollator collator;
XpsDocument doc = null;
bool endBatchWrite = false;
if (customCollator != null)
{
collator = customCollator;
}
else
{
doc = new XpsDocument(path, FileAccess.ReadWrite);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
collator = writer.CreateVisualsCollator();
collator.BeginBatchWrite();
}
int count = 1;
int flowDocumentCount = 0;
foreach (FlowDocument flowdoc in this.Documents)
{
if (pr != null && pr.Value.PageTo != 0 && (count < pr.Value.PageFrom || count > pr.Value.PageTo))
{
continue;
}
IDocumentPaginatorSource source = flowdoc;
DocumentPaginator p = source.DocumentPaginator;
p.PageSize = new Size(this.extentWidth, this.extentHeight);
p.ComputePageCount();
this.TotalPages += p.PageCount;
flowDocumentCount += p.PageCount;
}
int currentPage = 1;
foreach (FlowDocument flowdoc in this.Documents)
{
IDocumentPaginatorSource source = flowdoc;
DocumentPaginator p = source.DocumentPaginator;
p.PageSize = new Size(this.extentWidth, this.extentHeight);
p.ComputePageCount();
for (int i = 0; i < p.PageCount; ++i)
{
DocumentPage page = p.GetPage(i);
ContainerVisual v = new ContainerVisual();
DrawingVisual header = this.CreateHeader(
currentPage++,
this.TotalPages,
digitallySign,
headerText,
printAccount,
patientAlignment,
headerTextAlignment,
logoAlignment,
headerFontSize,
headerTypeFace);
ContainerVisual cv = new ContainerVisual();
cv.Children.Add(page.Visual);
// This offset is how far down the header will show. If the logo cuts off, it's because
// it extends below the Y variable in the line of code below in the new Vector(x,y)
cv.Offset = new Vector(0, 45);
v.Children.Add(cv);
v.Children.Add(header);
v.Transform = new ScaleTransform(0.96, 1);
v.Offset = new Vector(24, 0);
collator.Write(v);
endBatchWrite = true;
Util.LogAccess("Printing a Document", AccessLogComponent.HealthRecord, AccessLogAction.Print);
}
}
if (customCollator == null)
{
if (endBatchWrite == true)
{
collator.EndBatchWrite();
}
doc.Close();
}
// Minus on from the current page due to the fact that we start on page 1.
return this.HandleCreationError(currentPage - 1, flowDocumentCount, path, generatedNote, alwaysThrow, out error);
}
Example of Before and After: (All of the patient data is made up)
Pre-XPS
XPS
PatientHistoryUC.xaml Layout
What I want to happen
Depending on the amount of data being supplied to the XPS generated file, pagination occurs which moves excess contents to the next page rather than cutting off. I believe the BlockUIContainer is the main issue as it can't be split into two when the data becomes too much for the set page size (8 x 11).
Thank you for reading.

Add a colored box to the left of my item in a ComboBox

This is how I'm currently populating a combo box. Is there any way to be able to use a list like this, but also add a little color preview box to the left of the item?
private void PantsColor_ComboBox_Loaded(object sender, RoutedEventArgs e)
{
List<string> data = new List<string>();
data.Add("Blue");
data.Add("Red");
data.Add("Green");
data.Add("Orange");
data.Add("Pink");
data.Add("Purple");
var pantsColorComboBox = sender as ComboBox;
pantsColorComboBox.ItemsSource = data;
pantsColorComboBox.SelectedIndex = 0;
}
A color preview box to the left of the item can be added in ComboBox.ItemTemplate:
private void ColorComboBoxLoaded(object sender, RoutedEventArgs e)
{
var cbo = sender as ComboBox;
if (cbo == null)
return;
cbo.ItemsSource = new List<string> { "Blue", "Red", "Green", "Orange", "Pink", "Purple" };
}
<ComboBox SelectedIndex="0"
Loaded="ColorComboBoxLoaded">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="{Binding}" Height="20" Width="24" Margin="2"/>
<TextBlock Grid.Column="1" Margin="5,0" VerticalAlignment="Center" Text="{Binding}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Result:
note that SelectedIndex can be set in xaml. also note, that even if Background is set with string value, it is still applied properly because there is a built-in converter which creates Brush from color name (or hex ARGB code).
also all items can be set in xaml without any event handlers in code-behind:
<ComboBox SelectedIndex="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="{Binding}" Height="20" Width="24" Margin="2"/>
<TextBlock Grid.Column="1" Margin="5,0" VerticalAlignment="Center" Text="{Binding}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Items>
<system:String>Blue</system:String>
<system:String>Red</system:String>
<system:String>Green</system:String>
<system:String>Orange</system:String>
<system:String>Pink</system:String>
<system:String>Purple</system:String>
</ComboBox.Items>
</ComboBox>
You need to use Datatemplate for Combobox items.
<ComboBox x:Name="pantsColorComboBox" Height="30" Width="200">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="20" Height="20" Background="{Binding Key}"></TextBlock>
<TextBlock Text="{Binding Value}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Your itemsource will be modified to a dictionary.
var data = new Dictionary<SolidColorBrush,string>();
data.Add(Brushes.Blue,"Blue");
data.Add(Brushes.Red,"Red");
data.Add(Brushes.Green, "Green");
data.Add(Brushes.Orange, "Orange");
data.Add(Brushes.Pink, "Pink");
data.Add(Brushes.Purple, "Purple");
var pantsColorComboBox = sender as ComboBox;
pantsColorComboBox.ItemsSource = data;
pantsColorComboBox.SelectedIndex = 0;

How to make a StackPanel visible only when the DragCompleted + Condition is matched

I am working on a UWP app in which my requirement is to show a StackPanel only when Drag is completed in a grid view and condition is met too.
I am making a drag and re-order like game. I want the StackPanel (Containing Result and Buttons) appear when not only a single drag is completed but all the items are sorted too.
I have got everything else working fine. Only StackPanel is problem. It appears just after on drag completed whether the condition is met or not.
Following are the screenshots, code and more briefing !
ScreenShot
XAML for GridView
<GridView Name="GameDisplay"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Grid.Row="1"
Grid.Column="1"
CanDrag="True"
CanDragItems="True"
CanReorderItems="True"
SelectionMode="Single"
AllowDrop="True"
DragItemsCompleted="GameDisplay_DragItemsCompleted">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Width="60"
Height="60"
Background="Black">
<TextBlock Text="{Binding}"
FontFamily="BriLliant"
FontSize="48"
FontWeight="light"
Foreground="White"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal"
MaximumRowsOrColumns="10"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
XAML For StackPanel
<StackPanel Grid.Row="1" Grid.Column="1" Name="GameFinished" Background="#9900ff" Width="800" HorizontalAlignment="Center" VerticalAlignment="Center" Height="auto">
<Grid Name="InnerGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Name="BtnsContainer" Grid.Column="1" Width="auto" Height="auto" Margin="0 10 0 0">
<Grid Name="BtnsGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Name="txtBannerType"
Width="auto"
Height="auto"
Grid.Row="0"
Text="Well Done !"
FontSize="72"
FontWeight="Bold"
FontFamily="BriLliant"
Foreground="White"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Name="txtTimeSpent"
Width="200"
Height="auto"
Grid.Row="1"
Text=""
FontSize="48"
FontWeight="Light"
FontFamily="BriLliant"
Foreground="White"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Name="txtScore"
Width="200"
Height="auto"
Grid.Row="2"
Text="Score : 0"
FontSize="48"
FontWeight="Light"
FontFamily="BriLliant"
Foreground="White"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Name="txtBestScore"
Width="200"
Height="auto"
Grid.Row="3"
Text="Best Score : 0"
FontSize="48"
FontWeight="Light"
FontFamily="BriLliant"
Foreground="White"
TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Button Name="RestartGame"
Width="200"
Height="70"
Grid.Row="4"
Background="Black"
Content="Restart"
FontSize="48"
FontWeight="Bold"
FontFamily="BriLliant"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0 20 0 0"
Click="RestartGame_Click"/>
<Button Name="MainMenu"
Width="200"
Height="70"
Grid.Row="5"
Background="Black"
Content="Main Menu"
FontSize="48"
FontWeight="Bold"
FontFamily="BriLliant"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0 20 0 0"
Click="MainMenu_Click"/>
</Grid>
</StackPanel>
</Grid>
</StackPanel>
C# Events
private void Page_Loaded(object sender, RoutedEventArgs e)
{
lib.New(GameDisplay);//For Starting New Game
}
private void GameDisplay_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
lib.completed(GameDisplay);//When the tiles are re-ordered
}
Class that is handling BackEnd
class Library
{
//BackEnd for Math Game
private const int size = 5;
private const int total = size * size;
private DateTime timer;
private ObservableCollection<int> items = new ObservableCollection<int>();
private Random random = new Random((int)DateTime.Now.Ticks);
public void show(string content, string title)
{
IAsyncOperation<IUICommand> command = new MessageDialog(content, title).ShowAsync();
}
private List<int> select(int start, int finish, int total)
{
int number;
List<int> numbers = new List<int>();
while ((numbers.Count < total))
{
number = random.Next(start, finish + 1);
if ((!numbers.Contains(number)) || (numbers.Count < 1))
{
numbers.Add(number);
}
}
return numbers;
}
private bool winner()
{
return items.OrderBy(o => o).ToList().SequenceEqual(items.ToList());
}
private void layout(ref GridView grid)
{
timer = DateTime.UtcNow;
grid.IsEnabled = true;
grid.ItemsSource = null;
items = new ObservableCollection<int>();
List<int> numbers = select(1, total, total);
int index = 0;
while (index < numbers.Count)
{
items.Add(numbers[index]);
index++;
}
grid.ItemsSource = items;
}
public void New(GridView grid)
{
layout(ref grid);
}
public void completed(GridView grid)
{
string congo = "";
if (winner())
{
TimeSpan duration = (DateTime.UtcNow - timer).Duration();
congo = string.Format("Time: {0}:{1}:{2}", duration.Hours, duration.Minutes, duration.Seconds);
grid.IsEnabled = false;
}
}
}
Above is the game screen. When I drag and re-order a tile an event is fired in which a method runs until all the tiles are dragged and reordered according to the index of list that is containing all these numbers.
StackPanel only waits for one Drag. Is there any way to add StackPanel into the condition that checks for list sort?? Something like Data-Binding??
If you find anything missing, wrong or the question is already solved before. Please let me know explicitly !
Thanks...
Since you have already use winner() method to judge whether the game is ended and also invoke this in GameDisplay_DragItemsCompleted method. So actually the condition is already met in your completed method (every drag completed to judge whether game is over ). We just need to set the Visibility property of StackPanel to visible.
Update complete method as follows:
public void completed(GridView grid,StackPanel stackpanel)
{
string congo = "";
if (winner())
{
TimeSpan duration = (DateTime.UtcNow - timer).Duration();
congo = string.Format("Time: {0}:{1}:{2}", duration.Hours, duration.Minutes, duration.Seconds);
grid.IsEnabled = false;
stackpanel.Visibility = Visibility.Visible;
}
}
Update GameDisplay_DragItemsCompleted method as follows:
private void GameDisplay_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
{
lib.completed(GameDisplay,GameFinished);//When the tiles are re-ordered
}
Pay attention that in default the StackPanel should be collapsed. Update XAML code about StackPanel as follows:
<StackPanel Grid.Row="1" Grid.Column="1" Name="GameFinished" Background="#9900ff" Width="800" HorizontalAlignment="Center" VerticalAlignment="Center" Height="auto" Visibility="Collapsed">
And you can reset the state for StackPanel and GridView when game restart. Code as follows:
private void RestartGame_Click(object sender, RoutedEventArgs e)
{
GameDisplay.IsEnabled = true;
GameFinished.Visibility = Visibility.Collapsed;
}
And the result:
The problem is, by this code in winner function
return items.OrderBy(o => o).ToList().SequenceEqual(items.ToList()); }
You just check if list items is equal to list items. It will always return true.
According to this msdn document orderBy orders the list itself does not return a copy of it.
If you are going to use this method, create another list to compare with curent list and keep that list sorted.
You may create two bool flags and check them in both methods OnDragComplete and OnChangeCondition like this:
private void OnDragComplete()
{
_isDragCompleted = true;
if (_isDragCompleted && _isConditionChanged)
{
CollapseStackPanel();
}
}
private void OnChangeCondition()
{
_isConditionChanged = true;
if (_isDragCompleted && _isConditionChanged)
{
CollapseStackPanel();
}
}
private void CollapseStackPanel()
{
_isDragCompleted = false;
_isConditionChanged = false;
StackPanel.Visibility = Visibility.Collapsed;
}

Creating and filling a NxN grid in UWP Xaml

I am trying to create a UWP puzzle game, I want to cut the picture into n parts and then show the pieces in a grid.
My problem is, how to force a certain NxN style. Right now I have to maximize the window in order to see a 3x3 grid, if I shrink either side, it will converge to a 2 column, 1 column grid. Is there a way to handle this?
This is what I have done, I know the RowDefinition is manually right now, until I figure out a better way to do that.
<UserControl
x:Class="PictureSplitter.Views.PictureView"
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"
d:DesignHeight="300"
d:DesignWidth="400">
<GridView ItemsSource="{Binding Splitter.PuzzlePositions}">
<GridView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="2">
<Grid x:Name="picGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image Source="{Binding Piece.ImageSource}" />
</Grid>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</UserControl>
This are two example images:
There are probably couple of ways to do that, here is another one. I've modified the UserControl so that it automatically adjusts items size to show them as square grid, when page size changes and/or collection changes.
The UserControl XAML code:
<UserControl
x:Class="MyControls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyControls"
Name="myControl">
<GridView Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" ItemsSource="{Binding ElementName=myControl, Path=Items}"
Width="{Binding ElementName=myControl, Path=CurrentWidth}" HorizontalAlignment="Center"
Height="{Binding Width, RelativeSource={RelativeSource Self}}">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="Margin" Value="0"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<Border Padding="10" Width="{Binding ElementName=myControl, Path=ElementSize}" Height="{Binding ElementName=Width, RelativeSource={RelativeSource Self}}">
<Border BorderBrush="Red" BorderThickness="3">
<Image Source="ms-appx:///Assets/StoreLogo.png" Stretch="UniformToFill"/>
</Border>
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</UserControl>
UserControl code behind:
public sealed partial class MyUserControl : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public IList Items
{
get { return (IList)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IList), typeof(MyUserControl),
new PropertyMetadata(0, (s, e) =>
{
if (Math.Sqrt((e.NewValue as IList).Count) % 1 != 0)
Debug.WriteLine("Bad Collection");
}));
public void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (Math.Sqrt(Items.Count) % 1 != 0) Debug.WriteLine("Bad Collection");
RaiseProperty(nameof(ElementSize));
}
private double currentWidth;
public double CurrentWidth
{
get { return currentWidth; }
set { currentWidth = value; RaiseProperty(nameof(CurrentWidth)); RaiseProperty(nameof(ElementSize)); }
}
public double ElementSize => (int)(currentWidth / (int)Math.Sqrt(Items.Count)) - 1;
public MyUserControl()
{
this.InitializeComponent();
}
}
The MainPage XAML:
<Grid>
<local:MyUserControl x:Name="myControl" Items="{Binding MyItems}"/>
<Button Content="Add" Click="Button_Click"/>
</Grid>
MainPage code behind:
public sealed partial class MainPage : Page
{
private ObservableCollection<int> myItems = new ObservableCollection<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
public ObservableCollection<int> MyItems
{
get { return myItems; }
set { myItems = value; }
}
public MainPage()
{
this.InitializeComponent();
DataContext = this;
MyItems.CollectionChanged += myControl.Items_CollectionChanged;
}
protected override Size MeasureOverride(Size availableSize)
{
myControl.CurrentWidth = Math.Min(availableSize.Height, availableSize.Width);
return base.MeasureOverride(availableSize);
}
private void Button_Click(object sender, RoutedEventArgs e) => MyItems.Add(3);
}
The program starts with "Bad Collection" - there are 8 items, so you can't make a square grid from them, but as soon as you click the provided button - the collection's count changes to 9 and the grid should update itself.
It looks like you are doing this by way of MVVM, so I think you need to have a property for your Rows and Columns from your ViewModel. And then you need to have a Converter to supply the coordinate for your pieces .... OR an Attached property.
This will give you an idea:
<Window.Resources>
<System:Int64 x:Key="X">3</System:Int64>
<System:Int64 x:Key="Y">3</System:Int64>
</Window.Resources>
<Grid x:Name="myGrid" Loaded="Grid_Loaded">
// You can bind column and row
// <Button Content="image1" Grid.Column="{Binding column}" Grid.Row="{Binding row}"/>
<Button Content="image1" Grid.Column="0" Grid.Row="0"/>
<Button Content="image2" Grid.Column="1" Grid.Row="0"/>
<Button Content="image3" Grid.Column="2" Grid.Row="0"/>
<Button Content="image4" Grid.Column="0" Grid.Row="1"/>
<Button Content="image5" Grid.Column="1" Grid.Row="1"/>
<Button Content="image6" Grid.Column="2" Grid.Row="1"/>
<Button Content="image7" Grid.Column="0" Grid.Row="2"/>
<Button Content="image8" Grid.Column="1" Grid.Row="2"/>
<Button Content="image9" Grid.Column="2" Grid.Row="2"/>
</Grid>
private void Grid_Loaded(object sender, RoutedEventArgs e)
{
Int64 X = (Int64) this.FindResource("X");
Int64 Y = (Int64) this.FindResource("Y");
for (Int64 i = 0; i < X; i++)
{
ColumnDefinition c = new ColumnDefinition();
myGrid.ColumnDefinitions.Add(c);
}
for (Int64 i = 0; i < (int)Y; i++)
{
RowDefinition r = new RowDefinition();
myGrid.RowDefinitions.Add(r);
}
}
I have used a ListView with GridView as it's View property. And it is working fine.
<ListView x:Name="ImageList" Width="210" Height="210">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style TargetType="Control">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding sq1}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding sq2}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding sq3}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
var imgBox = new BitmapImage(new Uri(#"/images/cellbkg.jpg", UriKind.Relative));
var source = new[] { new { sq1 = imgBox, sq2 = imgBox, sq3 = imgBox }, new { sq1 = imgBox, sq2 = imgBox, sq3 = imgBox }, new { sq1 = imgBox, sq2 = imgBox, sq3 = imgBox } };
ImageList.ItemsSource = source;
This code produces below output, and don't get collapsed if you reduce window size :
If this is what you want, you can add columns dynamically using below approach. For NxN matrix, you have to add only N columns, binding will take care of rest :
GridView view = (GridView)ImageList.View;
view.Columns.Add(new GridViewColumn());

Categories

Resources