Dec 9, 2013

WPF - 7 (სტილები და რესურსები)


WPF ში შესაძლებელია თითოეულ ელემენტს შევუცვალოთ თვისება, როგორიცაა სიმაღლე, ფონტის ფორმა, ტიპი, მოცულობა და სხვა. მაგალითისთვის, რომ ავიღოთ ღილაკი მისი დიზაინის გაკეთება შესაძლებელია შემდეგი კოდით, რომელიც ყველაფერს მაინც ბოლომდე ვერ ფარავს.
<Button Height="25" Width="120" Background="Blue" Content="დამაჭირე"/>
ეს ყველაფერი მართლაც ძლიერი იარაღია, მაგრამ როდესაც აპლიკაციაში სულ მცირე ხუთი ღილაკია და ყველა ღილაკისთვის დახვეწილი დიზაინის გაკეთება გვინდა, რომელშიც საკმაოდ ბევრი რამ მეორდება, თითოეულის ხელით გაწერა უხერხულობასა და კოდის გადიდებას იწვევს, რის შემდეგაც იწყება თავის ტკივილები და ერთი ხაზის შეცვლას მოსდევს მთლიანი დიზაინის შეცვლის მოთხოვნა, რაც არც ისე მარტივია ასეთ დაქსაქსულ კოდში, ამიტომ გთავაზობთ გზას, რომელიც მოახდენს დაწერილი სტილების ლოკალიზებას და რაც შეიძლება შემოკლებას, რაც კოდის ხელახლა გამოყენებასაც უზრუნველყოფს და უფრო მოქნილს ხდის მთლიანად სისტემას.


სტილები, ვფიქრობ, რომ საკმაოდ მოცულობითი უნდა გამომივიდეს და წინასწარ განვსაზღვრავ, რომ  ამ ნაწილში ვისაუბრებ ზოგადად სტილის განსაზღვრაზე.
სტილის განსაზღვრისას ვიყენებთ <style > საკვანძო სიტყვას, რომლის შიგნით მას გააჩნია Setter თვისება, რომელსაც ორი თვისება აქვს თავის მხრივ - property და value. იმის მიხედვით თუ რა ელემენტს ენიშნება ეს სტილი, რომელის მითითებაც ხდება Style ის თვისების TargetType ის გამოყენებით, property ველში ჩნდება ის თვისებები, რომელიც დამახასიათებელია ამ ელემენტისათვის, ხოლო Value თვისებაში ის მნიშვნელობები, რომელებიც შესაბამის თვისებაზე შეიძლება დაენიშნოს.
<Button Content="დამაჭირე">
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="Height" Value="25"/>
                    <Setter Property="Width" Value="120"/>
                    <Setter Property="Background" Value="YellowGreen"/>
                </Style>
            </Button.Style>
        </Button>
ეს არის იგივე ღილაკი, რომელსაც მხოლოდ ფერი შევუცვალე და გამოვიყენე სტილები. მაგრამ სტილის ღილაკშივე გამოყენება არ არის მოსახერხებელი, რადგან ისევ იმის გაკეთება მოგვიწევს, რაც ზემოთ აღვნიშნე - ყველა კონტროლისთვის ხელოვნურად სტილის გაწერა. საჭიროა, რომ კოდის განზოგადება მოვახდინოთ და უფრო ნაკლები რესურსი გამოვიყენოთ.
<Window x:Class="App_for_blogpost_tutorials.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>
        <Style x:Key="ButtonStyle" TargetType="Button">
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="120"/>
            <Setter Property="Background" Value="YellowGreen"/>
        </Style>
    </Window.Resources>

    <StackPanel>
        <Button Content="დამაჭირე" Style="{StaticResource ButtonStyle}"/>
        <Button Content="მეორე ღილაკი" Style="{StaticResource ButtonStyle}"/>
    </StackPanel >
</Window>
როგორც ვხედავთ მხოლოდ სტილის მითითება გახდა საჭირო, რომ ერთსა და იმავე ღილაკს იგივე ფერის უკანა ფერი ჰქონოდა და სიმაღლე და სიგანე დაყენებულიყო, შესაბამისად 25 და 120. ახლა კი მინდა თქვენი ყურადღება გავამახვილო ერთ რამეზე, შეხედეთ ქვემოთ მოცემულ კოდს
<Style x:Key="ButtonStyle" TargetType="Button">
რომელსაც თუ ჩამოვაცილებთ x:Key ს და გადავაკეთებთ ასე
<Style TargetType="Button">
ეს იმას ნიშნავს, რომ ამის ქვეშ განსაზღვრული სტილი დაენიშნება ყველა ღილაკს. ანუ ღილაკებისათვის შეიქმნება გლობალური სტილი და აღარც იქნება საჭირო
<Button Content="მეორე ღილაკი" Style="{StaticResource ButtonStyle}"/>
Style="{StaticResource ButtonStyle}" ის მითითება, რაც ერთის მხრივ შეიძლება გამოსადეგი მოგეჩვენოთ, მაგრამ არც ისე. რა თქმა უნდა ეს ყველაფერი გამოსადეგია თუ ისეთ ზოგად სტილს ააწყობთ, რომ ყველა ღილაკს უნდა ჰქონდეს ეს თვისება ამ მნიშვნელობაზე დაყენებული, მაგალითად როგორიცაა FontFamily და მსგავსი.
შედარებით რომ გავართულოთ შემდეგი ხაზი შევცვლოთ და უკანა ფონი დავაყენოთ წრფივი გრადაცია, ერთი ფერის მაგივრად, რომელიც უფრო მიმზიდველი და დახვეწილია ჩემი აზრით
<Setter Property="Background" Value="YellowGreen"/>
<Setter Property="Background">
                <Setter.Value>
                    <LinearGradientBrush StartPoint="0,0"EndPoint="0,1">
                        <GradientStop Offset="0.0" Color="LightCyan" />
                        <GradientStop Offset="0.14" Color="Cyan" />
                        <GradientStop Offset="0.7" Color="DarkCyan" />
                    </LinearGradientBrush>
                </Setter.Value>
            </Setter>

და ჩვენი მიღებული შედეგი ასე გამოიყურება 
<Window x:Class="App_for_blogpost_tutorials.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>
        <Style x:Key="ButtonStyle" TargetType="Button">
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="120"/>
        </Style>
    </Window.Resources>

    <StackPanel x:Name="myContainer">
        <StackPanel.Resources>
            <LinearGradientBrush x:Key="MyGradientBrush" StartPoint="0,0"
                                 EndPoint="0.3,1">
                <GradientStop Offset="0.0" Color="LightCyan"/>
                <GradientStop Offset="0.14" Color="Cyan"/>
                <GradientStop Offset="0.7" Color="DarkCyan"/>
            </LinearGradientBrush>
        </StackPanel.Resources>
       
        <Button Style="{StaticResource ButtonStyle}" Background="{StaticResource MyGradientBrush}"/>
       
    </StackPanel>
</Window>
მოცემულ მაგალითში რესურსების განსაზღვრა ხდება კონტეინერ stackpanel ში, ხოლო წინა მაგალითში მე ისინი განვსაზღვრე window ში, რომელსაც ასევე გააჩნია resources მახასიათებელი. FrameworkElement განსაზღვრავს თვისება Resources, რომლის ტიპი არის ResourceDictionary. სწორედ ამის წყალობით არის შესაძლებელი ყველა კლასში რესურსის განსაზღვრა, თუ ის მემკვიდრეა FramworkElement ის.
რესურსები ხელმისაწვდომია იერარქიულად, რაც იმას ნიშნავს, რომ მე თუ window ში განვსაზღვრე რაღაც რესურსი, ის ხელმისაწვდომი იქნება მთლიანად იმ ელემენტებისთვის, რომელიც ამ კონტეინერს ეკუთვნის, ხოლო თუ ამ რესურსს მე stackpanel ში განვსაზღვრავდი ეს რესურსი მხოლოდ stackpanel ის შიგნით იქნებოდა ხელმისაწვდომი.
თუ საჭიროა ერთი და იმავე სტილი დაენიშნოს ერთზე მეტ ფანჯარას, მაშინ ლოგიკურია დავსვათ კითხვა:
-         როგორ დავუნიშნოთ window ს სტილი, რომელიც არაფერშია მოთავსებული?!
აქ პრობლემა გადის იმ xaml ფაილიდან, რომელშიც ხშირად ვმუშაობდი ხოლმე, და მიდის App.xaml ფაილთან, რომელიც ამ შემთხვევაში იმისთვის გვჭირდება, რომ განვსაზღვროთ გლობალური რესურსი. აპლიკაციის სტილები ხელმისაწვდომია ყველა ფანჯრისათვის, უკეთ რომ წარმოიდგინოთ თქვენი window ვირტუალურად ჩასვათ App.xaml ში, რომელიც კონტეინერის როლს ითამაშებს ყველა window ელემენტისათვის.
<Application x:Class="App_for_blogpost_tutorials.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        
    </Application.Resources>
</Application>

როდესაც ფაილს გახსნით მსგავსი კოდი დაგხვდებათ. შემდეგში გავარჩევთ, როგორ მოვახდინოთ ამ ფაილით მანიპულირება და როგორ შევძლოთ დასახული მიზნების განხორციელება.

არსებობს რამდენიმე სისტემური რესურსი, რომელიც წარმოადგენს ფერებს და ისინი ხელმისაწვდომია მთელი აპლიკაციის გამოყენებისას და შექმნისას. ეს რესურსები განსაზღვრულია SystemColors, SystetemFonts და SystemParameters კლასებში. შესაბამისად რომ მოვყვეთ თავიდან პირველ კლასში არის ფერის მოდიფიკაციები საზღვრებისთვის, კონტროლებისთვის და ფანჯრებისთვის, რომელიც ხელმისაწვდომია მთელს აპლიკაციაში. მეორე კლასი შეიცავს რესურსებს ფონტებისათვის, და მესამე კლასი კი ზომებს კონტროლებისთვის.

რესურსებზე წვდომა კოდის გამოყენებით

რესურსებზე ხელმისაწვდომად პირდაპირ კოდიდან შესაძლებელია FrameworkElement ის მიერ იმპლემენტირებული მეთოდი FindResource(). შესაძლებელია FindResource() გამოძახება ნებისმიერი WPF ობიექტთან.
ამ ღილაკს არ აქვს მითითებული უკანა ფონი, მაგრამ გააჩნია Click მოვლენა
<Button Name="button1" Width="220" Height="50" Margin="5" Click="button1_Click">
            რესურსის პროგრამულად მინიჭება
        </Button >

ამ მოვლენის იმპლემენტაციისას ზემოთ ხსენებული მეთოდი გამოიყენება, რომელიც დაგეხმარებათ მითითებული რესურსის მოძებნაში. ფუნჯის სახელი არის GradientBrush, რომელიც შეგიძლიათ სურვილისებრ შეცვალოთ. ხოლო, რაც შეეხება ძებნას, ის ხდება იერარქიულად, და ნაპოვნი ფუნჯი ენიშნება Background თვისებას, რომელიც button კონტროლისაა. ეს ფუნჯი შესრულებულია stackpanel ში.
<StackPanel x:Name="myContainer">
        <StackPanel.Resources>
            <LinearGradientBrush x:Key="GradientBrush" StartPoint="0,0" EndPoint="0.3,1">
                <GradientStop Offset="0.0" Color="LightCyan" />
                <GradientStop Offset="0.14" Color="Cyan" />
                <GradientStop Offset="0.7" Color="DarkCyan" />
            </LinearGradientBrush>
        </StackPanel.Resources>
        <Button Name="button1" Width="220" Height="50" Margin="5" Click="button1_Click" >
            Apply Resource Programmatically
        </Button>
    </StackPanel>

მოვლენის კოდი კი იქნება შემდეგი
public void button1_Click(object sender, RoutedEventArgs e)
        {
            Control ctrl = sender as Control;
            ctrl.Background = ctrl.FindResource("GradientBrush"as Brush;
        }
დინამიური რესურსები

სტატიკური რესურსების გამოყენებისას, რომელსაც აქამდე ვიყენებდი, ისინი იძებნებიან ჩატვირთვის დროს. თუ რესურსი შეიცვლება პროგრამის გაშვების განმავლობაში მაშინ უნდა გამოიყენოთ DynamicResource მონიშვნის გაფართოება.
<Button Name="button2" Width="200" Height="50" Foreground="White" Margin="5"
Background="{DynamicResource GradientBrush}" Content="ChangeResource"
                Click="button2_Click" />

ზემოთ მოცემული მაგალითი იყენებს იმავე რესურსს, რომელიც მე უკვე დიდი ხანია განსაზღვრული მაქვს. წინა მაგალითი იყენებდა StaticResource ს, ხოლო ამ მაგალითში კი ვიყენებთ დინამიურ რესურსს. მოვლენის კოდი კი პროგრამულად ცვლის რესურსს. მოვლენის კოდი წმენდს StackPanel ის რესურსებს და ამატებს ახალ რესურსს იმავე DGradientBrush, ეს რესურსი ჰგავს წინა რესურს. უბრალოდ იყენებს სხვა ფერებს.
private void button2_Click(object sender, RoutedEventArgs e)
        {
            myContainer.Resources.Clear();
            var brush = new LinearGradientBrush
            {
                StartPoint = new Point(0, 0),
                EndPoint = new Point(0, 1)
            };

            brush.GradientStops = new GradientStopCollection()
                {
                new GradientStop(Colors.White, 0.0),
                new GradientStop(Colors.Yellow, 0.14),
                new GradientStop(Colors.YellowGreen, 0.7)
                };
            myContainer.Resources.Add("DGradientBrush", brush);
        }

როდესაც პროგრამას გაუშვებთ რესურსი იცვლება დინამიურად ღილაკზე დაჭერისას. ხოლო ღილაკი სტატიკური რესურსით გამოიყურება იგივენაირად.

Resource Dictionaries

თუ ერთი და იმავე რესურსს იყენებს სხვადასხვა აპლიკაცია, მაშინ უფრო ჭკვიანურია მათი ცალკე ფაილში მოთავსება, რომელსაც ეწოდება resource Dictionary. ის შეიძლება დარეგისტრირდეს ასამბლეაში და გაზიარდეს რამდენიმე აპლიკაციაში.

ამისთვის პროექტს უნდა დავუმატოთ Resource Dictionary. Visual Studio ს მენიუს გამოყენებით შემდეგნაირად ხდება Resource Dictionary ის დამატება - Project -> Add New Item -> Resource Dictionary(WPF).მე გამოყენებული მაქვს Dictionary1.xaml რომელში ჩვენ შეგვიძლია სტილები განვსაზღვროთ და ამ ფაილის გამოყენებით უკვე აპლიკაციის მასშტაბით გამოვიყენოთ ისინი.

დავუშვათ განვსაზღვრეთ ამ ფაილში რამდენიმე სტილი. ახლა საჭიროა, რომ ისინი ჩანდეს მთელი აპლიკაციის მასშტაბით, ამიტომ საჭიროა მისი რეგისტრაცია.  ამისთვის უნდა შევიდეთ app.xaml ში, სადაც შემდეგი ცვლილებები უნდა შევიტანოთ

<Application x:Class="StylesAndResources.App"
StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries >
                                <ResourceDictionary Source="Dictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>


სადაც Dictionary1.xaml პირდაპირ პროექტის solution შია მოთავსებული.

No comments:

Post a Comment